d57ed2e5c9bcc5b1a9960259f3c864c68c0e80e2
[cps.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2023-2024 Nordix Foundation
4  *  ================================================================================
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  *  SPDX-License-Identifier: Apache-2.0
18  *  ============LICENSE_END=========================================================
19  */
20
21 package org.onap.cps.ncmp.dmi.rest.stub.controller;
22
23 import static org.onap.cps.ncmp.api.NcmpResponseStatus.SUCCESS;
24
25 import com.fasterxml.jackson.core.JsonProcessingException;
26 import com.fasterxml.jackson.databind.JsonNode;
27 import com.fasterxml.jackson.databind.ObjectMapper;
28 import io.cloudevents.CloudEvent;
29 import io.cloudevents.core.builder.CloudEventBuilder;
30 import java.net.URI;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.UUID;
36 import lombok.RequiredArgsConstructor;
37 import lombok.extern.slf4j.Slf4j;
38 import org.json.simple.parser.JSONParser;
39 import org.json.simple.parser.ParseException;
40 import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.DataOperationRequest;
41 import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.DmiDataOperationRequest;
42 import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.DmiOperationCmHandle;
43 import org.onap.cps.ncmp.dmi.rest.stub.utils.ResourceFileReaderUtil;
44 import org.onap.cps.ncmp.events.async1_0_0.Data;
45 import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent;
46 import org.onap.cps.ncmp.events.async1_0_0.Response;
47 import org.onap.cps.ncmp.impl.utils.EventDateTimeFormatter;
48 import org.springframework.beans.factory.annotation.Value;
49 import org.springframework.context.ApplicationContext;
50 import org.springframework.core.io.Resource;
51 import org.springframework.core.io.ResourceLoader;
52 import org.springframework.http.HttpStatus;
53 import org.springframework.http.ResponseEntity;
54 import org.springframework.kafka.core.KafkaTemplate;
55 import org.springframework.web.bind.annotation.DeleteMapping;
56 import org.springframework.web.bind.annotation.GetMapping;
57 import org.springframework.web.bind.annotation.PathVariable;
58 import org.springframework.web.bind.annotation.PostMapping;
59 import org.springframework.web.bind.annotation.PutMapping;
60 import org.springframework.web.bind.annotation.RequestBody;
61 import org.springframework.web.bind.annotation.RequestHeader;
62 import org.springframework.web.bind.annotation.RequestMapping;
63 import org.springframework.web.bind.annotation.RequestParam;
64 import org.springframework.web.bind.annotation.RestController;
65
66 @RestController
67 @RequestMapping("${rest.api.dmi-stub-base-path}")
68 @RequiredArgsConstructor
69 @Slf4j
70 public class DmiRestStubController {
71
72     private static final String DEFAULT_TAG = "tagD";
73     private static final String DEFAULT_PASSTHROUGH_OPERATION = "read";
74     private static final String dataOperationEventType = "org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent";
75     private static final Map<String, String> moduleSetTagPerCmHandleId = new HashMap<>();
76     private final KafkaTemplate<String, CloudEvent> cloudEventKafkaTemplate;
77     private final ObjectMapper objectMapper;
78     private final ApplicationContext applicationContext;
79     @Value("${app.ncmp.async-m2m.topic}")
80     private String ncmpAsyncM2mTopic;
81     @Value("${delay.module-references-delay-ms}")
82     private long moduleReferencesDelayMs;
83     @Value("${delay.module-resources-delay-ms}")
84     private long moduleResourcesDelayMs;
85     @Value("${delay.read-data-for-cm-handle-delay-ms}")
86     private long readDataForCmHandleDelayMs;
87     @Value("${delay.write-data-for-cm-handle-delay-ms}")
88     private long writeDataForCmHandleDelayMs;
89
90     /**
91      * This code defines a REST API endpoint for adding new the module set tag mapping. The endpoint receives the
92      * cmHandleId and moduleSetTag as request body and add into moduleSetTagPerCmHandleId map with the provided
93      * values.
94      *
95      * @param requestBody map of cmHandleId and moduleSetTag
96      * @return a ResponseEntity object containing the updated moduleSetTagPerCmHandleId map as the response body
97      */
98     @PostMapping("/v1/tagMapping")
99     public ResponseEntity<Map<String, String>> addTagForMapping(@RequestBody final Map<String, String> requestBody) {
100         moduleSetTagPerCmHandleId.putAll(requestBody);
101         return new ResponseEntity<>(requestBody, HttpStatus.CREATED);
102     }
103
104     /**
105      * This code defines a GET endpoint of  module set tag mapping.
106      *
107      * @return The map represents the module set tag mapping.
108      */
109     @GetMapping("/v1/tagMapping")
110     public ResponseEntity<Map<String, String>> getTagMapping() {
111         return ResponseEntity.ok(moduleSetTagPerCmHandleId);
112     }
113
114     /**
115      * This code defines a GET endpoint of  module set tag by cm handle ID.
116      *
117      * @return The map represents the module set tag mapping filtered by cm handle ID.
118      */
119     @GetMapping("/v1/tagMapping/ch/{cmHandleId}")
120     public ResponseEntity<String> getTagMappingByCmHandleId(@PathVariable final String cmHandleId) {
121         return ResponseEntity.ok(moduleSetTagPerCmHandleId.get(cmHandleId));
122     }
123
124     /**
125      * This code defines a REST API endpoint for updating the module set tag mapping. The endpoint receives the
126      * cmHandleId and moduleSetTag as request body and updates the moduleSetTagPerCmHandleId map with the provided
127      * values.
128      *
129      * @param requestBody map of cmHandleId and moduleSetTag
130      * @return a ResponseEntity object containing the updated moduleSetTagPerCmHandleId map as the response body
131      */
132
133     @PutMapping("/v1/tagMapping")
134     public ResponseEntity<Map<String, String>> updateTagMapping(@RequestBody final Map<String, String> requestBody) {
135         moduleSetTagPerCmHandleId.putAll(requestBody);
136         return ResponseEntity.noContent().build();
137     }
138
139     /**
140      * It contains a method to delete an entry from the moduleSetTagPerCmHandleId map.
141      * The method takes a cmHandleId as a parameter and removes the corresponding entry from the map.
142      *
143      * @return a ResponseEntity containing the updated map.
144      */
145     @DeleteMapping("/v1/tagMapping/ch/{cmHandleId}")
146     public ResponseEntity<String> deleteTagMappingByCmHandleId(@PathVariable final String cmHandleId) {
147         moduleSetTagPerCmHandleId.remove(cmHandleId);
148         return ResponseEntity.ok(String.format("Mapping of %s is deleted successfully", cmHandleId));
149     }
150
151     /**
152      * Get all modules for given cm handle.
153      *
154      * @param cmHandleId              The identifier for a network function, network element, subnetwork,
155      *                                or any other cm object by managed Network CM Proxy
156      * @param moduleReferencesRequest module references request body
157      * @return ResponseEntity response entity having module response as json string.
158      */
159     @PostMapping("/v1/ch/{cmHandleId}/modules")
160     public ResponseEntity<String> getModuleReferences(@PathVariable("cmHandleId") final String cmHandleId,
161                                                       @RequestBody final Object moduleReferencesRequest) {
162         delay(moduleReferencesDelayMs);
163         try {
164             log.info("Incoming DMI request body: {}",
165                     objectMapper.writeValueAsString(moduleReferencesRequest));
166         } catch (final JsonProcessingException jsonProcessingException) {
167             log.info("Unable to parse dmi data operation request to json string");
168         }
169         final String moduleResponseContent = getModuleResourceResponse(cmHandleId,
170                 "ModuleResponse.json");
171         log.info("cm handle: {} requested for modules", cmHandleId);
172         return ResponseEntity.ok(moduleResponseContent);
173     }
174
175     /**
176      * Retrieves module resources for a given cmHandleId.
177      *
178      * @param cmHandleId                 The identifier for a network function, network element, subnetwork,
179      *                                   or any other cm object by managed Network CM Proxy
180      * @param moduleResourcesReadRequest module resources read request body
181      * @return ResponseEntity response entity having module resources response as json string.
182      */
183     @PostMapping("/v1/ch/{cmHandleId}/moduleResources")
184     public ResponseEntity<String> retrieveModuleResources(
185             @PathVariable("cmHandleId") final String cmHandleId,
186             @RequestBody final Object moduleResourcesReadRequest) {
187         delay(moduleResourcesDelayMs);
188         final String moduleResourcesResponseContent = getModuleResourceResponse(cmHandleId,
189                 "ModuleResourcesResponse.json");
190         log.info("cm handle: {} requested for modules resources", cmHandleId);
191         return ResponseEntity.ok(moduleResourcesResponseContent);
192     }
193
194     /**
195      * Create resource data from passthrough operational or running for a cm handle.
196      *
197      * @param cmHandleId              The identifier for a network function, network element, subnetwork,
198      *                                or any other cm object by managed Network CM Proxy
199      * @param datastoreName           datastore name
200      * @param resourceIdentifier      resource identifier
201      * @param options                 options
202      * @param topic                   client given topic name
203      * @return (@ code ResponseEntity) response entity
204      */
205     @PostMapping("/v1/ch/{cmHandleId}/data/ds/{datastoreName}")
206     public ResponseEntity<String> getResourceDataForCmHandle(
207             @PathVariable("cmHandleId") final String cmHandleId,
208             @PathVariable("datastoreName") final String datastoreName,
209             @RequestParam(value = "resourceIdentifier") final String resourceIdentifier,
210             @RequestParam(value = "options", required = false) final String options,
211             @RequestParam(value = "topic", required = false) final String topic,
212             @RequestHeader(value = "Authorization", required = false) final String authorization,
213             @RequestBody final String requestBody) {
214         log.info("DMI AUTH HEADER: {}", authorization);
215         final String passthroughOperationType = getPassthroughOperationType(requestBody);
216         if (passthroughOperationType.equals("read")) {
217             delay(readDataForCmHandleDelayMs);
218         } else {
219             delay(writeDataForCmHandleDelayMs);
220         }
221         log.info("Logging request body {}", requestBody);
222
223         final String sampleJson = ResourceFileReaderUtil.getResourceFileContent(applicationContext.getResource(
224                 ResourceLoader.CLASSPATH_URL_PREFIX + "data/operational/ietf-network-topology-sample-rfc8345.json"));
225         return ResponseEntity.ok(sampleJson);
226     }
227
228     /**
229      * This method is not implemented for ONAP DMI plugin.
230      *
231      * @param topic                   client given topic name
232      * @param requestId               requestId generated by NCMP as an ack for client
233      * @param dmiDataOperationRequest list of operation details
234      * @return (@ code ResponseEntity) response entity
235      */
236     @PostMapping("/v1/data")
237     public ResponseEntity<Void> getResourceDataForCmHandleDataOperation(
238             @RequestParam(value = "topic") final String topic,
239             @RequestParam(value = "requestId") final String requestId,
240             @RequestBody final DmiDataOperationRequest dmiDataOperationRequest) {
241         delay(writeDataForCmHandleDelayMs);
242         try {
243             log.info("Request received from the NCMP to DMI Plugin: {}",
244                     objectMapper.writeValueAsString(dmiDataOperationRequest));
245         } catch (final JsonProcessingException jsonProcessingException) {
246             log.info("Unable to process dmi data operation request to json string");
247         }
248         dmiDataOperationRequest.getOperations().forEach(dmiDataOperation -> {
249             final DataOperationEvent dataOperationEvent = getDataOperationEvent(dmiDataOperation);
250             dmiDataOperation.getCmHandles().forEach(dmiOperationCmHandle -> {
251                 log.info("Module Set Tag received: {}", dmiOperationCmHandle.getModuleSetTag());
252                 dataOperationEvent.getData().getResponses().get(0).setIds(List.of(dmiOperationCmHandle.getId()));
253                 final CloudEvent cloudEvent = buildAndGetCloudEvent(topic, requestId, dataOperationEvent);
254                 cloudEventKafkaTemplate.send(ncmpAsyncM2mTopic, UUID.randomUUID().toString(), cloudEvent);
255             });
256         });
257         return new ResponseEntity<>(HttpStatus.ACCEPTED);
258     }
259
260     private CloudEvent buildAndGetCloudEvent(final String topic, final String requestId,
261                                              final DataOperationEvent dataOperationEvent) {
262         CloudEvent cloudEvent = null;
263         try {
264             cloudEvent = CloudEventBuilder.v1()
265                     .withId(UUID.randomUUID().toString())
266                     .withSource(URI.create("DMI"))
267                     .withType(dataOperationEventType)
268                     .withDataSchema(URI.create("urn:cps:" + dataOperationEventType + ":1.0.0"))
269                     .withTime(EventDateTimeFormatter.toIsoOffsetDateTime(
270                             EventDateTimeFormatter.getCurrentIsoFormattedDateTime()))
271                     .withData(objectMapper.writeValueAsBytes(dataOperationEvent))
272                     .withExtension("destination", topic)
273                     .withExtension("correlationid", requestId)
274                     .build();
275         } catch (final JsonProcessingException jsonProcessingException) {
276             log.error("Unable to parse event into bytes. cause : {}", jsonProcessingException.getMessage());
277         }
278         return cloudEvent;
279     }
280
281     private DataOperationEvent getDataOperationEvent(final DataOperationRequest dataOperationRequest) {
282         final Response response = new Response();
283
284         response.setOperationId(dataOperationRequest.getOperationId());
285         response.setStatusCode(SUCCESS.getCode());
286         response.setStatusMessage(SUCCESS.getMessage());
287         response.setIds(dataOperationRequest.getCmHandles().stream().map(DmiOperationCmHandle::getId).toList());
288         response.setResourceIdentifier(dataOperationRequest.getResourceIdentifier());
289         response.setOptions(dataOperationRequest.getOptions());
290         final String ietfNetworkTopologySample = ResourceFileReaderUtil
291                 .getResourceFileContent(applicationContext.getResource(
292                         ResourceLoader.CLASSPATH_URL_PREFIX
293                                 + "data/operational/ietf-network-topology-sample-rfc8345.json"));
294         final JSONParser jsonParser = new JSONParser();
295         try {
296             response.setResult(jsonParser.parse(ietfNetworkTopologySample));
297         } catch (final ParseException parseException) {
298             log.error("Unable to parse event result as json object. cause : {}", parseException.getMessage());
299         }
300         final List<Response> responseList = new ArrayList<>(1);
301         responseList.add(response);
302         final Data data = new Data();
303         data.setResponses(responseList);
304         final DataOperationEvent dataOperationEvent = new DataOperationEvent();
305         dataOperationEvent.setData(data);
306         return dataOperationEvent;
307     }
308
309     private String getModuleResourceResponse(final String cmHandleId, final String moduleResponseType) {
310         if (moduleSetTagPerCmHandleId.isEmpty()) {
311             log.info("Using default module responses of type ietfYang");
312             return ResourceFileReaderUtil.getResourceFileContent(applicationContext.getResource(
313                     ResourceLoader.CLASSPATH_URL_PREFIX
314                             + String.format("module/ietfYang-%s", moduleResponseType)));
315         }
316         final String moduleSetTag = moduleSetTagPerCmHandleId.getOrDefault(cmHandleId, DEFAULT_TAG);
317         final String moduleResponseFilePath = String.format("module/%s-%s", moduleSetTag, moduleResponseType);
318         final Resource moduleResponseResource = applicationContext.getResource(
319                 ResourceLoader.CLASSPATH_URL_PREFIX + moduleResponseFilePath);
320         log.info("Using module responses from : {}", moduleResponseFilePath);
321         return ResourceFileReaderUtil.getResourceFileContent(moduleResponseResource);
322     }
323
324     private String getPassthroughOperationType(final String requestBody) {
325         try {
326             final JsonNode rootNode = objectMapper.readTree(requestBody);
327             return rootNode.path("operation").asText();
328         } catch (final JsonProcessingException jsonProcessingException) {
329             log.error("Invalid JSON format. cause : {}", jsonProcessingException.getMessage());
330         }
331         return DEFAULT_PASSTHROUGH_OPERATION;
332     }
333
334     private void delay(final long milliseconds) {
335         try {
336             Thread.sleep(milliseconds);
337         } catch (final InterruptedException e) {
338             log.error("Thread sleep interrupted: {}", e.getMessage());
339             Thread.currentThread().interrupt();
340         }
341     }
342 }