diff options
Diffstat (limited to 'src')
14 files changed, 749 insertions, 204 deletions
diff --git a/src/main/java/org/onap/nbi/OnapComponentsUrlPaths.java b/src/main/java/org/onap/nbi/OnapComponentsUrlPaths.java index 9060249..80563d5 100644 --- a/src/main/java/org/onap/nbi/OnapComponentsUrlPaths.java +++ b/src/main/java/org/onap/nbi/OnapComponentsUrlPaths.java @@ -50,4 +50,8 @@ public final class OnapComponentsUrlPaths { public static final String MSO_CREATE_E2ESERVICE_INSTANCE_PATH = "/onap/so/infra/e2eServiceInstances/v3"; public static final String MSO_DELETE_E2ESERVICE_INSTANCE_PATH = "/onap/so/infra/e2eServiceInstances/v3/"; public static final String MSO_GET_E2EREQUEST_STATUS_PATH = "/onap/so/infra/e2eServiceInstances/v3/$serviceId/operations/$operationId"; -} + + // DMaaP Message Router REST Client + public static final String DMAAP_CONSUME_EVENTS = + "/events/$topic/$consumergroup/$consumerid?timeout=$timeout"; +}
\ No newline at end of file diff --git a/src/main/java/org/onap/nbi/apis/hub/HubResource.java b/src/main/java/org/onap/nbi/apis/hub/HubResource.java index 0f94802..ac073c1 100755 --- a/src/main/java/org/onap/nbi/apis/hub/HubResource.java +++ b/src/main/java/org/onap/nbi/apis/hub/HubResource.java @@ -1,23 +1,24 @@ /** * Copyright (c) 2018 Orange * - * 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 + * 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. + * 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. */ package org.onap.nbi.apis.hub; -import java.net.URI; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import org.onap.nbi.apis.hub.model.Subscriber; import org.onap.nbi.apis.hub.model.Subscription; +import org.onap.nbi.apis.hub.service.CheckDMaaPEventsManager; import org.onap.nbi.apis.hub.service.SubscriptionService; import org.onap.nbi.commons.JsonRepresentation; import org.onap.nbi.commons.MultiCriteriaRequestBuilder; @@ -42,67 +43,80 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; @RestController @RequestMapping("/hub") @EnableScheduling public class HubResource extends ResourceManagement { - Logger logger = LoggerFactory.getLogger(HubResource.class); + Logger logger = LoggerFactory.getLogger(HubResource.class); - @Autowired - MongoTemplate mongoTemplate; + @Autowired + MongoTemplate mongoTemplate; - @Autowired - SubscriptionService subscriptionService; + @Autowired + SubscriptionService subscriptionService; - @Autowired - MultiCriteriaRequestBuilder multiCriteriaRequestBuilder; + @Autowired + MultiCriteriaRequestBuilder multiCriteriaRequestBuilder; - @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<Object> createEventSubscription(@RequestBody Subscription subscription, - @RequestParam MultiValueMap<String, String> params) { - logger.debug("POST request for subscription : {}", subscription); - Subscriber subscriber = subscriptionService.createSubscription(subscription); - JsonRepresentation filter = new JsonRepresentation(params); - return this.createResponse(Subscription.createFromSubscriber(subscriber), filter); - - } - - @GetMapping(value = "/{subscriptionId}", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<Subscription> getSubscription(@PathVariable String subscriptionId) { + @Autowired + CheckDMaaPEventsManager checkDMaaPEventMAnager; + @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<Object> createEventSubscription(@RequestBody Subscription subscription, + @RequestParam MultiValueMap<String, String> params) { + logger.debug("POST request for subscription : {}", subscription); + Subscriber subscriber = subscriptionService.createSubscription(subscription); + JsonRepresentation filter = new JsonRepresentation(params); + return this.createResponse(Subscription.createFromSubscriber(subscriber), filter); - Optional<Subscriber> optionalSubscriber = subscriptionService.findSubscriptionById(subscriptionId); - if (!optionalSubscriber.isPresent()) { - return ResponseEntity.notFound().build(); - } - return ResponseEntity.ok(Subscription.createFromSubscriber(optionalSubscriber.get())); - } - - @GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<Object> findSubscribers(@RequestParam MultiValueMap<String, String> params) { + } - Query query = multiCriteriaRequestBuilder.buildRequest(params); - List<Subscriber> subscribers = mongoTemplate.find(query, Subscriber.class); - JsonRepresentation filter = new JsonRepresentation(params); - long totalCount = subscriptionService.countSubscription(); - HttpHeaders headers = new HttpHeaders(); - headers.add("X-Total-Count", String.valueOf(totalCount)); - headers.add("X-Result-Count", String.valueOf(subscribers.size())); - List<Subscription> subscriptions = subscribers.stream() - .map(Subscription::createFromSubscriber) - .collect(Collectors.toList()); + @GetMapping(value = "/{subscriptionId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<Subscription> getSubscription(@PathVariable String subscriptionId) { - return this.findResponse(subscriptions, filter, headers); + Optional<Subscriber> optionalSubscriber = + subscriptionService.findSubscriptionById(subscriptionId); + if (!optionalSubscriber.isPresent()) { + return ResponseEntity.notFound().build(); } + return ResponseEntity.ok(Subscription.createFromSubscriber(optionalSubscriber.get())); + } + + @GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<Object> findSubscribers( + @RequestParam MultiValueMap<String, String> params) { + + Query query = multiCriteriaRequestBuilder.buildRequest(params); + List<Subscriber> subscribers = mongoTemplate.find(query, Subscriber.class); + JsonRepresentation filter = new JsonRepresentation(params); + long totalCount = subscriptionService.countSubscription(); + HttpHeaders headers = new HttpHeaders(); + headers.add("X-Total-Count", String.valueOf(totalCount)); + headers.add("X-Result-Count", String.valueOf(subscribers.size())); + List<Subscription> subscriptions = + subscribers.stream().map(Subscription::createFromSubscriber).collect(Collectors.toList()); + + return this.findResponse(subscriptions, filter, headers); + + } + + /* + * Resource to test for DMaaP Integration for subscribing to AAI-EVENTs + */ + @GetMapping("/testaaievents") + @ResponseStatus(HttpStatus.OK) + public void testAAIEventListener() { + checkDMaaPEventMAnager.checkForDMaaPAAIEvents(); + } + + @DeleteMapping("/{subscriptionId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteSubscription(@PathVariable String subscriptionId) { + logger.debug("DELETE request for subscription id #{}", subscriptionId); + subscriptionService.deleteSubscription(subscriptionId); + } - @DeleteMapping("/{subscriptionId}") - @ResponseStatus(HttpStatus.NO_CONTENT) - public void deleteSubscription(@PathVariable String subscriptionId) { - logger.debug("DELETE request for subscription id #{}", subscriptionId); - subscriptionService.deleteSubscription(subscriptionId); - } } diff --git a/src/main/java/org/onap/nbi/apis/hub/model/EventType.java b/src/main/java/org/onap/nbi/apis/hub/model/EventType.java index 1702347..b4e1f1a 100644 --- a/src/main/java/org/onap/nbi/apis/hub/model/EventType.java +++ b/src/main/java/org/onap/nbi/apis/hub/model/EventType.java @@ -1,17 +1,15 @@ /** - * Copyright (c) 2018 Orange + * Copyright (c) 2018 Orange * - * 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 + * 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 + * 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. + * 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. */ package org.onap.nbi.apis.hub.model; @@ -20,36 +18,42 @@ import com.fasterxml.jackson.annotation.JsonValue; public enum EventType { - SERVICE_ORDER_CREATION("ServiceOrderCreationNotification"), + SERVICE_ORDER_CREATION("ServiceOrderCreationNotification"), - SERVICE_ORDER_STATE_CHANGE("ServiceOrderStateChangeNotification"), + SERVICE_ORDER_STATE_CHANGE("ServiceOrderStateChangeNotification"), - SERVICE_ORDER_ITEM_STATE_CHANGE("ServiceOrderItemStateChangeNotification"); + SERVICE_ORDER_ITEM_STATE_CHANGE("ServiceOrderItemStateChangeNotification"), - private String value; + SERVICE_CREATION("ServiceCreationNotification"), - EventType(String value) { - this.value = value; - } + SERVICE_ATTRIBUTE_VALUE_CHANGE("ServiceAttributeValueChangeNotification"), - @Override - public String toString() { - return String.valueOf(value); - } + SERVICE_REMOVE("ServiceRemoveNotification"); - @JsonCreator - public static EventType fromValue(String text) { - for (EventType b : EventType.values()) { - if (String.valueOf(b.value).equals(text)) { - return b; - } - } - return null; - } + private String value; - @JsonValue - public String value() { - return this.value; + EventType(String value) { + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static EventType fromValue(String text) { + for (EventType b : EventType.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } } + return null; + } + + @JsonValue + public String value() { + return this.value; + } } diff --git a/src/main/java/org/onap/nbi/apis/hub/model/ServiceInstanceEvent.java b/src/main/java/org/onap/nbi/apis/hub/model/ServiceInstanceEvent.java new file mode 100644 index 0000000..4bbafdc --- /dev/null +++ b/src/main/java/org/onap/nbi/apis/hub/model/ServiceInstanceEvent.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2019 Huawei + * + * 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. + */ + +package org.onap.nbi.apis.hub.model; + +import org.onap.nbi.apis.serviceorder.model.RelatedParty; +import org.springframework.data.annotation.Id; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ServiceInstanceEvent { + + @Id + @JsonProperty("id") + private String id = null; + + @JsonProperty("href") + private String href = null; + + @JsonProperty("name") + private String name = null; + + @JsonProperty("type") + private String type = "service-instance"; + + @JsonProperty("state") + private String state = null; + + @JsonProperty("relatedParty") + private RelatedParty relatedParty = null; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public RelatedParty getRelatedParty() { + return relatedParty; + } + + public void setRelatedParty(RelatedParty relatedParty) { + this.relatedParty = relatedParty; + } + +} diff --git a/src/main/java/org/onap/nbi/apis/hub/service/CheckDMaaPEventsManager.java b/src/main/java/org/onap/nbi/apis/hub/service/CheckDMaaPEventsManager.java new file mode 100644 index 0000000..6e2811f --- /dev/null +++ b/src/main/java/org/onap/nbi/apis/hub/service/CheckDMaaPEventsManager.java @@ -0,0 +1,199 @@ +/** + * Copyright (c) 2019 Huawei + * + * 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. + */ + +package org.onap.nbi.apis.hub.service; + +import java.io.IOException; +import java.net.URI; +import java.util.List; +import javax.annotation.PostConstruct; +import org.onap.nbi.OnapComponentsUrlPaths; +import org.onap.nbi.apis.hub.model.Event; +import org.onap.nbi.apis.hub.model.EventType; +import org.onap.nbi.apis.hub.model.ServiceInstanceEvent; +import org.onap.nbi.apis.hub.repository.SubscriberRepository; +import org.onap.nbi.apis.serviceorder.model.RelatedParty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + + +@Service +public class CheckDMaaPEventsManager { + + public static final String RESPONSE_STATUS = "response status : "; + public static final String RETURNS = " returns "; + public static final String ERROR_ON_CALLING = "error on calling "; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private SubscriberRepository subscriberRepository; + + @Autowired + private NotifierService notifier; + + @Value("${dmaap.host}") + private String dmaapHostname; + + @Value("${dmaap.topic}") + private String topic; + + @Value("${dmaap.consumergroup}") + private String consumerGroup; + + @Value("${dmaap.consumerid}") + private String consumerId; + + @Value("${dmaap.timeout}") + private String timeout; + + private final Logger logger = LoggerFactory.getLogger(CheckDMaaPEventsManager.class); + + private String dmaapGetEventsUrl; + + @PostConstruct + private void setUpAndLogDMaaPUrl() { + dmaapGetEventsUrl = new StringBuilder().append(dmaapHostname) + .append(OnapComponentsUrlPaths.DMAAP_CONSUME_EVENTS).toString(); + logger.info("DMaaP Get Events url : " + dmaapGetEventsUrl); + } + + public void checkForDMaaPAAIEvents() { + ObjectMapper mapper = new ObjectMapper(); + + + String dmaapGetEventsUrlFormated = dmaapGetEventsUrl.replace("$topic", topic); + dmaapGetEventsUrlFormated = dmaapGetEventsUrlFormated.replace("$consumergroup", consumerGroup); + dmaapGetEventsUrlFormated = dmaapGetEventsUrlFormated.replace("$consumerid", consumerId); + dmaapGetEventsUrlFormated = dmaapGetEventsUrlFormated.replace("$timeout", timeout); + + List<String> dmaapResponse = callDMaaPGetEvents(dmaapGetEventsUrlFormated); + if (!CollectionUtils.isEmpty(dmaapResponse)) { + for (int i = 0; i < dmaapResponse.size(); i++) { + String aaiEventString = dmaapResponse.get(i); + if (logger.isDebugEnabled()) { + logger.debug("aai event returned was {}", aaiEventString); + } + try { + JsonNode jsonNode = mapper.readValue(aaiEventString, JsonNode.class); + JsonNode eventHeader = jsonNode.get("event-header"); + String aaiEventEntityType = eventHeader.get("entity-type").asText(); + String action = eventHeader.get("action").asText(); + if (logger.isDebugEnabled()) { + logger.debug("aaiEventEntityType is {} and action is {}", aaiEventEntityType, action); + } + if (aaiEventEntityType.equals("service-instance")) { + { + // parse the AAI-EVENT service-instance tree + ServiceInstanceEvent serviceInstanceEvent = new ServiceInstanceEvent(); + RelatedParty relatedParty = new RelatedParty(); + JsonNode entity = jsonNode.get("entity"); + relatedParty.setId(entity.get("global-customer-id").asText()); + relatedParty.setName(entity.get("subscriber-name").asText()); + serviceInstanceEvent.setRelatedParty(relatedParty); + JsonNode childServiceSubscription = entity.get("service-subscriptions"); + JsonNode serviceSubscriptions = childServiceSubscription.get("service-subscription"); + JsonNode serviceSubscription = serviceSubscriptions.get(0); + String serviceSubscriptionPrint = serviceSubscription.toString(); + JsonNode childserviceInstances = serviceSubscription.get("service-instances"); + JsonNode serviceInstances = childserviceInstances.get("service-instance"); + JsonNode serviceInstance = serviceInstances.get(0); + serviceInstanceEvent.setId(serviceInstance.get("service-instance-id").asText()); + serviceInstanceEvent.setState(serviceInstance.get("orchestration-status").asText()); + if (action.equals("CREATE")) { + if (logger.isDebugEnabled()) { + logger.debug("sending service inventory event to listeners"); + } + processEvent( + EventFactory.getEvent(EventType.SERVICE_CREATION, serviceInstanceEvent)); + } else if (action.equals("DELETE")) { + processEvent(EventFactory.getEvent(EventType.SERVICE_REMOVE, serviceInstanceEvent)); + } else if (action.equals("UPDATE")) { + processEvent(EventFactory.getEvent(EventType.SERVICE_ATTRIBUTE_VALUE_CHANGE, + serviceInstanceEvent)); + } + + + } + + } + + } catch (JsonParseException e) { + logger.error(" unable to Parse AAI Event JSON String {}, exception is", aaiEventString, + e.getMessage()); + } catch (JsonMappingException e) { + logger.error(" unable to Map AAI Event JSON String {} to Java Pojo, exception is", + aaiEventString, e.getMessage()); + } catch (IOException e) { + logger.error("IO Error when parsing AAI Event JSON String {} ", aaiEventString, + e.getMessage()); + } + } + } + } + + public List<String> callDMaaPGetEvents(String dmaapGetEventsUrlFormated) { + + if (logger.isDebugEnabled()) { + logger.debug("Calling DMaaP Url : " + dmaapGetEventsUrlFormated); + } + UriComponentsBuilder callURI = UriComponentsBuilder.fromHttpUrl(dmaapGetEventsUrlFormated); + ResponseEntity<Object> response = callDMaaP(callURI.build().encode().toUri()); + return (List<String>) response.getBody(); + + } + + private ResponseEntity<Object> callDMaaP(URI callURI) { + ResponseEntity<Object> response = + restTemplate.exchange(callURI, HttpMethod.GET, buildRequestHeader(), Object.class); + + if (logger.isDebugEnabled()) { + logger.debug("response body : {} ", response.getBody().toString()); + logger.debug("response status : {}", response.getStatusCodeValue()); + } + return response; + } + + private HttpEntity<String> buildRequestHeader() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add("Accept", "application/json"); + httpHeaders.add("Content-Type", "application/json"); + return new HttpEntity<>("parameters", httpHeaders); + } + + /** + * Retrieve subscribers that match an event and fire notification asynchronously + * + * @param event + */ + private void processEvent(Event event) { + subscriberRepository.findSubscribersUsingEvent(event).forEach(sub -> notifier.run(sub, event)); + } + +} diff --git a/src/main/java/org/onap/nbi/apis/hub/service/DMaaPEventsScheduler.java b/src/main/java/org/onap/nbi/apis/hub/service/DMaaPEventsScheduler.java new file mode 100644 index 0000000..20bc2d9 --- /dev/null +++ b/src/main/java/org/onap/nbi/apis/hub/service/DMaaPEventsScheduler.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2019 Huawei + * + * 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. + */ + +package org.onap.nbi.apis.hub.service; + +import java.io.FileNotFoundException; +import java.io.IOException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Profile("default") +@Service +@EnableScheduling +public class DMaaPEventsScheduler { + + @Autowired + CheckDMaaPEventsManager checkDMaaPEventsManager; + + @Scheduled(fixedDelayString = "${dmaapCheck.schedule}", + initialDelayString = "${dmaapCheck.initial}") + private void processDMaaPEvents() { + checkDMaaPEventsManager.checkForDMaaPAAIEvents(); + + } +} + diff --git a/src/main/java/org/onap/nbi/apis/hub/service/EventFactory.java b/src/main/java/org/onap/nbi/apis/hub/service/EventFactory.java index e935a1c..ed34322 100644 --- a/src/main/java/org/onap/nbi/apis/hub/service/EventFactory.java +++ b/src/main/java/org/onap/nbi/apis/hub/service/EventFactory.java @@ -1,75 +1,95 @@ /** * Copyright (c) 2018 Orange * - * 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 + * 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. + * 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. */ package org.onap.nbi.apis.hub.service; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.MappingJsonFactory; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.UUID; import org.onap.nbi.apis.hub.model.Event; import org.onap.nbi.apis.hub.model.EventType; +import org.onap.nbi.apis.hub.model.ServiceInstanceEvent; import org.onap.nbi.apis.serviceorder.model.ServiceOrder; import org.onap.nbi.apis.serviceorder.model.ServiceOrderItem; import org.onap.nbi.commons.JacksonFilter; import org.onap.nbi.commons.JsonRepresentation; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MappingJsonFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; public class EventFactory { - private static final ObjectMapper mapper = new ObjectMapper(new MappingJsonFactory()); + private static final ObjectMapper mapper = new ObjectMapper(new MappingJsonFactory()); - public static Event getEvent(EventType eventType, ServiceOrder serviceOrder, ServiceOrderItem serviceOrderItem) { - Event event = new Event(); - event.setEventId(UUID.randomUUID().toString()); - event.setEventDate(new Date()); - event.setEventType(eventType.value()); + public static Event getEvent(EventType eventType, ServiceOrder serviceOrder, + ServiceOrderItem serviceOrderItem) { + Event event = new Event(); + event.setEventId(UUID.randomUUID().toString()); + event.setEventDate(new Date()); + event.setEventType(eventType.value()); - DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - mapper.setDateFormat(df); + DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + mapper.setDateFormat(df); - JsonNode serviceOrderJson = mapper.valueToTree(filterServiceOrder(serviceOrder)); + JsonNode serviceOrderJson = mapper.valueToTree(filterServiceOrder(serviceOrder)); - if (EventType.SERVICE_ORDER_ITEM_STATE_CHANGE.equals(eventType)) { - JsonNode serviceOrderItemJson = mapper.valueToTree(serviceOrderItem); - ((ObjectNode) serviceOrderJson).putArray("orderItem").add(serviceOrderItemJson); - } + if (EventType.SERVICE_ORDER_ITEM_STATE_CHANGE.equals(eventType)) { + JsonNode serviceOrderItemJson = mapper.valueToTree(serviceOrderItem); + ((ObjectNode) serviceOrderJson).putArray("orderItem").add(serviceOrderItemJson); + } - event.setEvent(serviceOrderJson); + event.setEvent(serviceOrderJson); - return event; - } + return event; + } + + public static Event getEvent(EventType eventType, ServiceInstanceEvent serviceInstanceEvent) { + Event event = new Event(); + event.setEventId(UUID.randomUUID().toString()); + event.setEventDate(new Date()); + event.setEventType(eventType.value()); + + DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + mapper.setDateFormat(df); + JsonNode serviceInstanceJson = mapper.valueToTree(serviceInstanceEvent); - /** - * Filter ServiceOrderObject to produce a lightweight object that fit the eventBody specification - * @param serviceOrder - * @return - */ - private static Object filterServiceOrder(final ServiceOrder serviceOrder) { + event.setEvent(serviceInstanceJson); - Object filteredServiceOrder = null; + return event; + } - if (serviceOrder != null) { - JsonRepresentation jsonRepresentation = new JsonRepresentation(); - jsonRepresentation.add("id").add("href").add("externalId").add("state").add("orderDate").add - ("completionDateTime").add("orderItem"); - filteredServiceOrder = JacksonFilter.createNode(serviceOrder, jsonRepresentation); - } + /** + * Filter ServiceOrderObject to produce a lightweight object that fit the eventBody specification + * + * @param serviceOrder + * @return + */ + private static Object filterServiceOrder(final ServiceOrder serviceOrder) { - return filteredServiceOrder; + Object filteredServiceOrder = null; + + if (serviceOrder != null) { + JsonRepresentation jsonRepresentation = new JsonRepresentation(); + jsonRepresentation.add("id").add("href").add("externalId").add("state").add("orderDate") + .add("completionDateTime").add("orderItem"); + + filteredServiceOrder = JacksonFilter.createNode(serviceOrder, jsonRepresentation); } + + return filteredServiceOrder; + } } diff --git a/src/main/java/org/onap/nbi/apis/hub/service/NotifierService.java b/src/main/java/org/onap/nbi/apis/hub/service/NotifierService.java index 90cc7a4..80b6331 100755 --- a/src/main/java/org/onap/nbi/apis/hub/service/NotifierService.java +++ b/src/main/java/org/onap/nbi/apis/hub/service/NotifierService.java @@ -1,14 +1,15 @@ /** * Copyright (c) 2018 Orange * - * 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 + * 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. + * 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. */ package org.onap.nbi.apis.hub.service; @@ -26,19 +27,19 @@ import org.springframework.web.client.RestTemplate; @Service public class NotifierService { - private final Logger logger = LoggerFactory.getLogger(NotifierService.class); + private final Logger logger = LoggerFactory.getLogger(NotifierService.class); - @Autowired - RestTemplate restTemplate; - - @Async - public void run(Subscriber subscriber, @Valid Event event) { - try { - restTemplate.postForEntity(subscriber.getCallback(), event, Object.class); - } catch (BackendFunctionalException e) { - logger.error(" unable to post event to {} , receive {}, {}", subscriber.getCallback(), e.getHttpStatus(), - e.getBodyResponse()); - } + @Autowired + RestTemplate restTemplate; + @Async + public void run(Subscriber subscriber, @Valid Event event) { + try { + restTemplate.postForEntity(subscriber.getCallback(), event, Object.class); + } catch (BackendFunctionalException e) { + logger.error(" unable to post event to {} , receive {}, {}", subscriber.getCallback(), + e.getHttpStatus(), e.getBodyResponse()); } + + } } diff --git a/src/main/java/org/onap/nbi/configuration/AppContext.java b/src/main/java/org/onap/nbi/configuration/AppContext.java new file mode 100644 index 0000000..fb26799 --- /dev/null +++ b/src/main/java/org/onap/nbi/configuration/AppContext.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2019 Huawei + * + * 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. + */ + +package org.onap.nbi.configuration; + +import java.util.concurrent.Executor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; + +@Configuration +@EnableAsync +public class AppContext extends WebMvcConfigurationSupport { + @Bean + public Executor taskExecutor() { + return new SimpleAsyncTaskExecutor(); + } +} diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index b959b47..ca04a05 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -60,6 +60,13 @@ so.owning.entity.id = 6b5b6b70-4e9a-4f6f-8b7b-cbd7cf990c6e so.owning.entity.name = OE-generic so.project.name = Project-generic +# DMAAP +dmaap.host = http://127.0.0.1:8091 +dmaap.topic = AAI-EVENT +dmaap.consumergroup = NBICG1 +dmaap.consumerid = NBIC1 +dmaap.timeout = 2000 + # MSB msb.enabled = false diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 633e029..70d640c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -44,6 +44,8 @@ serviceOrder.schedule = 5000 serviceOrder.initial = 1 executionTask.schedule = 2000 executionTask.initial = 1 +dmaapCheck.schedule = 10000 +dmaapCheck.initial = 1 # SDC sdc.host = http://10.0.3.1:8080 @@ -64,6 +66,13 @@ so.owning.entity.id = 6b5b6b70-4e9a-4f6f-8b7b-cbd7cf990c6e so.owning.entity.name = OE-generic so.project.name = Project-generic +# DMAAP +dmaap.host = http://10.0.6.1:3904 +dmaap.topic = AAI-EVENT +dmaap.consumergroup = NBICG1 +dmaap.consumerid = NBIC1 +dmaap.timeout = 2000 + # MSB msb.enabled = true msb.discovery.host = msb_discovery diff --git a/src/test/java/org/onap/nbi/api/listener/ListenerResource.java b/src/test/java/org/onap/nbi/api/listener/ListenerResource.java index 1b60109..8fb276d 100644 --- a/src/test/java/org/onap/nbi/api/listener/ListenerResource.java +++ b/src/test/java/org/onap/nbi/api/listener/ListenerResource.java @@ -1,18 +1,18 @@ /** * Copyright (c) 2019 Orange * - * 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 + * 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. + * 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. */ package org.onap.nbi.api.listener; -import com.fasterxml.jackson.databind.JsonNode; import java.net.URI; import java.util.ArrayList; import java.util.Collection; @@ -34,81 +34,97 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import com.fasterxml.jackson.databind.JsonNode; @RestController @RequestMapping("/test/listener") public class ListenerResource extends ResourceManagement { - Logger logger = LoggerFactory.getLogger(ListenerResource.class); - - static Map<String, JsonNode> events = new ConcurrentHashMap<>(); + Logger logger = LoggerFactory.getLogger(ListenerResource.class); - /* - listener resource test for hub resource - */ - @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<JsonNode> postListener(@RequestBody JsonNode event) { - if (logger.isDebugEnabled()) { - logger.debug("POST event from nbi : {}", event.toString()); - } - String eventId = event.get("eventId").asText(); - events.put(eventId, event); - - URI location = ServletUriComponentsBuilder - .fromCurrentRequest() - .path("/{id}") - .buildAndExpand(eventId) - .toUri(); + static Map<String, JsonNode> events = new ConcurrentHashMap<>(); - return ResponseEntity.created(location).body(event); + /* + * listener resource test for hub resource + */ + @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<JsonNode> postListener(@RequestBody JsonNode event) { + if (logger.isDebugEnabled()) { + logger.debug("POST event from nbi : {}", event.toString()); } - - - @GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<Collection<JsonNode>> findEvents(@RequestParam MultiValueMap<String, String> params) { - Collection<JsonNode> values = new ArrayList<>(); - String serviceOrderId = params.getFirst("serviceOrderId"); - if(StringUtils.isNotEmpty(serviceOrderId)) { - for (JsonNode jsonNode : events.values()) { - String id = jsonNode.get("event").get("id").asText(); - logger.info("found event with service order id : "+id); - if(id.equals(serviceOrderId)) { - values.add(jsonNode); - } - } - if(!values.isEmpty()) { - return ResponseEntity.ok(values); - } else { - logger.error("cannot found events with service order id : "+serviceOrderId); - return ResponseEntity.notFound().build(); - } - } else { - values=events.values(); + String eventId = event.get("eventId").asText(); + logger.info("putting eventId {} in the events map", eventId); + events.put(eventId, event); + + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}") + .buildAndExpand(eventId).toUri(); + + return ResponseEntity.created(location).body(event); + } + + + @GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<Collection<JsonNode>> findEvents( + @RequestParam MultiValueMap<String, String> params) { + logger.info("called listener get with params {} : " + params.toString()); + Collection<JsonNode> values = new ArrayList<>(); + String serviceOrderId = params.getFirst("serviceOrderId"); + String serviceInstanceId = params.getFirst("serviceInstanceId"); + if (StringUtils.isNotEmpty(serviceOrderId)) { + for (JsonNode jsonNode : events.values()) { + String id = jsonNode.get("event").get("id").asText(); + logger.info("found event with service order id : " + id); + if (id.equals(serviceOrderId)) { + values.add(jsonNode); + } + } + if (!values.isEmpty()) { + return ResponseEntity.ok(values); + } else { + logger.error("cannot found events with service order id : " + serviceOrderId); + return ResponseEntity.notFound().build(); + } + } else if (StringUtils.isNotEmpty(serviceInstanceId)) { + for (JsonNode jsonNode : events.values()) { + String id = jsonNode.get("event").get("id").asText(); + logger.info("found event with service Instance id : " + id); + if (id.equals(serviceInstanceId)) { + values.add(jsonNode); } + } + if (!values.isEmpty()) { return ResponseEntity.ok(values); + } else { + logger.error("cannot found events with service instance id : " + serviceInstanceId); + return ResponseEntity.notFound().build(); + } + } else { + values = events.values(); } + return ResponseEntity.ok(values); + } - @GetMapping(value = "/{eventId}", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<Object> getEvent(@PathVariable String eventId) { + @GetMapping(value = "/{eventId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<Object> getEvent(@PathVariable String eventId) { - return ResponseEntity.ok(events.get(eventId)); + return ResponseEntity.ok(events.get(eventId)); - } + } - @DeleteMapping(value = "/{eventId}", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<Object> deleteEvent(@PathVariable String eventId) { + @DeleteMapping(value = "/{eventId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<Object> deleteEvent(@PathVariable String eventId) { - events.remove(eventId); - return ResponseEntity.noContent().build(); + events.remove(eventId); + return ResponseEntity.noContent().build(); - } + } - @DeleteMapping(produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<Object> deleteEvents() { + @DeleteMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<Object> deleteEvents() { - events.clear(); - return ResponseEntity.noContent().build(); + events.clear(); + return ResponseEntity.noContent().build(); - } + } } diff --git a/src/test/resources/karatetest/features/03--Subscriber.feature b/src/test/resources/karatetest/features/03--Subscriber.feature index 6e4b884..e4704ea 100644 --- a/src/test/resources/karatetest/features/03--Subscriber.feature +++ b/src/test/resources/karatetest/features/03--Subscriber.feature @@ -154,4 +154,98 @@ When method delete Then status 204 Given path 'serviceOrder',serviceOrderId When method delete +Then status 204 + + +Scenario: testSubscriberWithTestListenerForServiceInventorCreationEvents +* def listenerUrl = nbiBaseUrl + "/test/listener" +Given path 'test/listener' +When method delete +Then status 204 +Given path 'hub' +And request { id : 'id', callback : '#(listenerUrl)' , query : 'eventType = ServiceCreationNotification' } +When method post +Then status 201 +Given path 'hub' +When method get +And def hubId = $[0].id +Given path 'hub/testaaievents' +Then status 200 +When method get +Given path 'test/listener' +And params {serviceInstanceId : 'new-test5'} +And retry until responseStatus == 200 +When method get +And assert response.length == 1 +And match $[0] contains { eventId : '#notnull' , eventType : 'ServiceCreationNotification' , eventDate : '#notnull' , event :'#notnull'} +And def eventId = $[0].eventId +And def eventDate = $[0].eventDate +And call checkDateFormat(eventDate) +Given path 'hub',hubId +When method delete +Then status 204 +Given path 'test/listener',eventId +When method delete +Then status 204 + +Scenario: testSubscriberWithTestListenerForServiceInventoryUpdateEvents +* def listenerUrl = nbiBaseUrl + "/test/listener" +Given path 'test/listener' +When method delete +Then status 204 +Given path 'hub' +And request { id : 'id', callback : '#(listenerUrl)' , query : 'eventType = ServiceAttributeValueChangeNotification' } +When method post +Then status 201 +Given path 'hub' +When method get +And def hubId = $[0].id +Given path 'hub/testaaievents' +Then status 200 +When method get +Given path 'test/listener' +And params {serviceInstanceId : 'new-test5'} +And retry until responseStatus == 200 +When method get +And assert response.length == 1 +And match $[0] contains { eventId : '#notnull' , eventType : 'ServiceAttributeValueChangeNotification' , eventDate : '#notnull' , event :'#notnull'} +And def eventId = $[0].eventId +And def eventDate = $[0].eventDate +And call checkDateFormat(eventDate) +Given path 'hub',hubId +When method delete +Then status 204 +Given path 'test/listener',eventId +When method delete +Then status 204 + +Scenario: testSubscriberWithTestListenerForServiceInventoryRemoveEvents +* def listenerUrl = nbiBaseUrl + "/test/listener" +Given path 'test/listener' +When method delete +Then status 204 +Given path 'hub' +And request { id : 'id', callback : '#(listenerUrl)' , query : 'eventType = ServiceRemoveNotification' } +When method post +Then status 201 +Given path 'hub' +When method get +And def hubId = $[0].id +Given path 'hub/testaaievents' +Then status 200 +When method get +Given path 'test/listener' +And params {serviceInstanceId : 'new-test5'} +And retry until responseStatus == 200 +When method get +And assert response.length == 1 +And match $[0] contains { eventId : '#notnull' , eventType : 'ServiceRemoveNotification' , eventDate : '#notnull' , event :'#notnull'} +And def eventId = $[0].eventId +And def eventDate = $[0].eventDate +And call checkDateFormat(eventDate) +Given path 'hub',hubId +When method delete +Then status 204 +Given path 'test/listener',eventId +When method delete Then status 204
\ No newline at end of file diff --git a/src/test/resources/mappings/dmaap_get_aaievents.json b/src/test/resources/mappings/dmaap_get_aaievents.json new file mode 100644 index 0000000..6b5c630 --- /dev/null +++ b/src/test/resources/mappings/dmaap_get_aaievents.json @@ -0,0 +1,16 @@ +{ + "request": { + "method": "GET", + "url": "/events/AAI-EVENT/NBICG1/NBIC1?timeout=2000" + }, + "response": { + "status": 200, + "jsonBody": [ + "{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"service-instance\",\"top-entity-type\":\"customer\",\"entity-link\":\"/aai/v14/business/customers/customer/testcustomer2/service-subscriptions/service-subscription/HSIA_Service/service-instances/service-instance/new-test5\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"CREATE\",\"sequence-number\":\"0\",\"id\":\"c3654954-9802-4b46-a397-6e12c74d2254\",\"source-name\":\"AAI\",\"version\":\"v14\",\"timestamp\":\"20190301-10:20:47:154\"},\"entity\":{\"global-customer-id\":\"testcustomer2\",\"subscriber-name\":\"testcustomer2\",\"service-subscriptions\":{\"service-subscription\":[{\"service-type\":\"HSIA_Service\",\"service-instances\":{\"service-instance\":[{\"service-instance-id\":\"new-test5\",\"resource-version\":\"1551435647134\",\"service-instance-name\":\"testname\",\"orchestration-status\":\"assigned\"}]}}]}}}", + "{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"service-instance\",\"top-entity-type\":\"customer\",\"entity-link\":\"/aai/v14/business/customers/customer/testcustomer2/service-subscriptions/service-subscription/HSIA_Service/service-instances/service-instance/new-test5\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"UPDATE\",\"sequence-number\":\"0\",\"id\":\"a746584a-72b7-4994-97b2-bc43ec24fd35\",\"source-name\":\"AAI\",\"version\":\"v14\",\"timestamp\":\"20190301-13:50:34:121\"},\"entity\":{\"global-customer-id\":\"testcustomer2\",\"subscriber-name\":\"testcustomer2\",\"service-subscriptions\":{\"service-subscription\":[{\"service-type\":\"HSIA_Service\",\"service-instances\":{\"service-instance\":[{\"service-instance-id\":\"new-test5\",\"resource-version\":\"1551448234103\",\"service-instance-name\":\"testname\",\"orchestration-status\":\"active\"}]}}]}}}","{\"cambria.partition\":\"AAI\",\"event-header\":{\"severity\":\"NORMAL\",\"entity-type\":\"service-instance\",\"top-entity-type\":\"customer\",\"entity-link\":\"/aai/v14/business/customers/customer/testcustomer2/service-subscriptions/service-subscription/HSIA_Service/service-instances/service-instance/new-test5\",\"event-type\":\"AAI-EVENT\",\"domain\":\"dev\",\"action\":\"DELETE\",\"sequence-number\":\"0\",\"id\":\"011c139e-d00f-4a55-b1fc-b319f16e0f70\",\"source-name\":\"AAI\",\"version\":\"v14\",\"timestamp\":\"20190301-13:53:09:590\"},\"entity\":{\"global-customer-id\":\"testcustomer2\",\"subscriber-name\":\"testcustomer2\",\"service-subscriptions\":{\"service-subscription\":[{\"service-type\":\"HSIA_Service\",\"service-instances\":{\"service-instance\":[{\"service-instance-id\":\"new-test5\",\"resource-version\":\"1551448234103\",\"service-instance-name\":\"testname\",\"orchestration-status\":\"active\"}]}}]}}}" + ], + "headers": { + "Content-Type": "application/json" + } + } +} |