Sonar Fixes
[policy/apex-pdp.git] / client / client-monitoring / src / main / java / org / onap / policy / apex / client / monitoring / rest / ApexMonitoringRestResource.java
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2016-2018 Ericsson. All rights reserved.
4  *  Modifications Copyright (C) 2020 Nordix Foundation.
5  * ================================================================================
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * SPDX-License-Identifier: Apache-2.0
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.apex.client.monitoring.rest;
23
24 import com.google.gson.Gson;
25 import com.google.gson.JsonArray;
26 import com.google.gson.JsonObject;
27 import java.io.PrintWriter;
28 import java.io.StringWriter;
29 import java.util.HashMap;
30 import java.util.LinkedList;
31 import java.util.List;
32 import java.util.Map;
33 import javax.ws.rs.Consumes;
34 import javax.ws.rs.GET;
35 import javax.ws.rs.Path;
36 import javax.ws.rs.Produces;
37 import javax.ws.rs.QueryParam;
38 import javax.ws.rs.core.MediaType;
39 import javax.ws.rs.core.Response;
40 import org.onap.policy.apex.core.deployment.ApexDeploymentException;
41 import org.onap.policy.apex.core.deployment.EngineServiceFacade;
42 import org.onap.policy.apex.model.basicmodel.concepts.ApexException;
43 import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
44 import org.onap.policy.apex.model.enginemodel.concepts.AxEngineModel;
45 import org.slf4j.ext.XLogger;
46 import org.slf4j.ext.XLoggerFactory;
47
48 /**
49  * The class represents the root resource exposed at the base URL<br> The url to access this resource would be in the
50  * form {@code <baseURL>/rest/....} <br> For example: a GET request to the following URL
51  * {@code http://localhost:18989/apexservices/rest/?hostName=localhost&port=12345}
52  *
53  * <b>Note:</b> An allocated {@code hostName} and {@code port} query parameter must be included in all requests.
54  * Datasets for different {@code hostName} are completely isolated from one another.
55  *
56  */
57 @Path("monitoring/")
58 @Produces(
59     { MediaType.APPLICATION_JSON })
60 @Consumes(
61     { MediaType.APPLICATION_JSON })
62
63 public class ApexMonitoringRestResource {
64     // Get a reference to the logger
65     private static final XLogger LOGGER = XLoggerFactory.getXLogger(ApexMonitoringRestResource.class);
66
67     // Recurring string constants
68     private static final String ERROR_CONNECTING_PREFIX = "Error connecting to Apex Engine Service at ";
69
70     // Set the maximum number of stored data entries to be stored for each engine
71     private static final int MAX_CACHED_ENTITIES = 50;
72
73     // Set up a map separated by host and engine for the data
74     private static final HashMap<String, HashMap<String, List<Counter>>> cache = new HashMap<>();
75
76     // Set up a map separated by host for storing the state of periodic events
77     private static final HashMap<String, Boolean> periodicEventsStateCache = new HashMap<>();
78
79     /**
80      * Query the engine service for data.
81      *
82      * @param hostName the host name of the engine service to connect to.
83      * @param port the port number of the engine service to connect to.
84      * @return a Response object containing the engines service, status and context data in JSON
85      */
86     @GET
87     public Response createSession(@QueryParam("hostName") final String hostName, @QueryParam("port") final int port) {
88         final Gson gson = new Gson();
89         final String host = hostName + ":" + port;
90         final EngineServiceFacade engineServiceFacade = getEngineServiceFacade(hostName, port);
91
92         try {
93             engineServiceFacade.init();
94         } catch (final ApexDeploymentException e) {
95             final String errorMessage = ERROR_CONNECTING_PREFIX + host;
96             LOGGER.warn(errorMessage + "<br>", e);
97             return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + e.getMessage())
98                             .build();
99         }
100
101         final JsonObject responseObject = new JsonObject();
102
103         // Engine Service data
104         responseObject.addProperty("engine_id", engineServiceFacade.getKey().getId());
105         responseObject.addProperty("model_id",
106                         engineServiceFacade.getApexModelKey() != null ? engineServiceFacade.getApexModelKey().getId()
107                                         : "Not Set");
108         responseObject.addProperty("server", hostName);
109         responseObject.addProperty("port", Integer.toString(port));
110         responseObject.addProperty("periodic_events", getPeriodicEventsState(host));
111
112         // Engine Status data
113         final JsonArray engineStatusList = new JsonArray();
114
115         for (final AxArtifactKey engineKey : engineServiceFacade.getEngineKeyArray()) {
116             try {
117                 final JsonObject engineStatusObject = new JsonObject();
118                 final AxEngineModel axEngineModel = engineServiceFacade.getEngineStatus(engineKey);
119                 engineStatusObject.addProperty("timestamp", axEngineModel.getTimeStampString());
120                 engineStatusObject.addProperty("id", engineKey.getId());
121                 engineStatusObject.addProperty("status", axEngineModel.getState().toString());
122                 engineStatusObject.addProperty("last_message", axEngineModel.getStats().getTimeStampString());
123                 engineStatusObject.addProperty("up_time", axEngineModel.getStats().getUpTime() / 1000L);
124                 engineStatusObject.addProperty("policy_executions", axEngineModel.getStats().getEventCount());
125                 engineStatusObject.addProperty("last_policy_duration",
126                                 gson.toJson(getValuesFromCache(host, engineKey.getId() + "_last_policy_duration",
127                                                 axEngineModel.getTimestamp(),
128                                                 axEngineModel.getStats().getLastExecutionTime()), List.class));
129                 engineStatusObject
130                                 .addProperty("average_policy_duration", gson.toJson(
131                                                 getValuesFromCache(host, engineKey.getId() + "_average_policy_duration",
132                                                                 axEngineModel.getTimestamp(),
133                                                                 (long) axEngineModel.getStats()
134                                                                                 .getAverageExecutionTime()),
135                                                 List.class));
136                 engineStatusList.add(engineStatusObject);
137             } catch (final ApexException e) {
138                 LOGGER.warn("Error getting status of engine with ID " + engineKey.getId() + "<br>", e);
139             }
140         }
141         responseObject.add("status", engineStatusList);
142
143         // Engine context data
144         final JsonArray engineContextList = new JsonArray();
145         for (final AxArtifactKey engineKey : engineServiceFacade.getEngineKeyArray()) {
146             try {
147                 final String engineInfo = engineServiceFacade.getEngineInfo(engineKey);
148                 if (engineInfo != null && !engineInfo.trim().isEmpty()) {
149                     final JsonObject engineContextObject = new JsonObject();
150                     engineContextObject.addProperty("id", engineKey.getId());
151                     engineContextObject.addProperty("engine_info", engineInfo);
152                     engineContextList.add(engineContextObject);
153                 }
154             } catch (final ApexException e) {
155                 LOGGER.warn("Error getting runtime information of engine with ID " + engineKey.getId() + "<br>", e);
156             }
157         }
158         responseObject.add("context", engineContextList);
159
160         return Response.ok(responseObject.toString(), MediaType.APPLICATION_JSON).build();
161     }
162
163     /**
164      * Start/Stop and Apex engine.
165      *
166      * @param hostName the host name of the engine service to connect to.
167      * @param port the port number of the engine service to connect to.
168      * @param engineId the id of the engine to be started/stopped.
169      * @param startStop the parameter to start/stop the engine. Expects either "Start" or "Stop"
170      * @return a Response object of type 200
171      */
172     @GET
173     @Path("startstop/")
174     public Response startStop(@QueryParam("hostName") final String hostName, @QueryParam("port") final int port,
175                     @QueryParam("engineId") final String engineId, @QueryParam("startstop") final String startStop) {
176         final EngineServiceFacade engineServiceFacade = getEngineServiceFacade(hostName, port);
177
178         try {
179             engineServiceFacade.init();
180         } catch (final ApexDeploymentException e) {
181             final String errorMessage = ERROR_CONNECTING_PREFIX + hostName + ":" + port;
182             LOGGER.warn(errorMessage + "<br>", e);
183             return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + e.getMessage())
184                             .build();
185         }
186
187         try {
188             final Map<String, String[]> parameterMap = new HashMap<>();
189             parameterMap.put("hostname", new String[]
190                 { hostName });
191             parameterMap.put("port", new String[]
192                 { Integer.toString(port) });
193             parameterMap.put("AxArtifactKey#" + engineId, new String[]
194                 { startStop });
195             final AxArtifactKey engineKey = ParameterCheck.getEngineKey(parameterMap);
196             if ("Start".equals(startStop)) {
197                 engineServiceFacade.startEngine(engineKey);
198             } else if ("Stop".equals(startStop)) {
199                 engineServiceFacade.stopEngine(engineKey);
200             }
201         } catch (final Exception e) {
202             final String errorMessage = "Error calling " + startStop + " on Apex Engine: " + engineId;
203             LOGGER.warn(errorMessage + "<br>", e);
204             final StringWriter sw = new StringWriter();
205             e.printStackTrace(new PrintWriter(sw));
206             return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + sw.toString())
207                             .build();
208         }
209
210         return Response.ok("{}").build();
211     }
212
213     /**
214      * Start/Stop Apex engine Periodic Events.
215      *
216      * @param hostName the host name of the engine service to connect to.
217      * @param port the port number of the engine service to connect to.
218      * @param engineId the id of the engine to be started/stopped.
219      * @param startStop the parameter to start/stop the engine. Expects either "Start" or "Stop"
220      * @param period the time between each event in milliseconds
221      * @return a Response object of type 200
222      */
223     @GET
224     @Path("periodiceventstartstop/")
225     public Response periodiceventStartStop(@QueryParam("hostName") final String hostName,
226                     @QueryParam("port") final int port, @QueryParam("engineId") final String engineId,
227                     @QueryParam("startstop") final String startStop, @QueryParam("period") final long period) {
228         final EngineServiceFacade engineServiceFacade = getEngineServiceFacade(hostName, port);
229         final String host = hostName + ":" + port;
230         try {
231             engineServiceFacade.init();
232             final Map<String, String[]> parameterMap = new HashMap<>();
233             parameterMap.put("hostname", new String[]
234                 { hostName });
235             parameterMap.put("port", new String[]
236                 { Integer.toString(port) });
237             parameterMap.put("AxArtifactKey#" + engineId, new String[]
238                 { startStop });
239             parameterMap.put("period", new String[]
240                 { Long.toString(period) });
241             final AxArtifactKey engineKey = ParameterCheck.getEngineKey(parameterMap);
242             if ("Start".equals(startStop)) {
243                 engineServiceFacade.startPerioidicEvents(engineKey, period);
244                 setPeriodicEventsState(host, true);
245             } else if ("Stop".equals(startStop)) {
246                 engineServiceFacade.stopPerioidicEvents(engineKey);
247                 setPeriodicEventsState(host, false);
248             }
249         } catch (final ApexDeploymentException e) {
250             final String errorMessage = ERROR_CONNECTING_PREFIX + host;
251             LOGGER.warn(errorMessage + "<br>", e);
252             return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + e.getMessage())
253                             .build();
254         }
255
256         return Response.ok("{}").build();
257     }
258
259     /**
260      * Check if periodic events are running.
261      *
262      * @param host the engine's host url
263      * @return a boolean stating if periodic events are running for a given host
264      */
265     private Boolean getPeriodicEventsState(final String host) {
266         if (periodicEventsStateCache.containsKey(host)) {
267             return periodicEventsStateCache.get(host);
268         } else {
269             return false;
270         }
271     }
272
273     /**
274      * Sets the state of periodic events for a host.
275      *
276      * @param host the engine's host url
277      * @param boolean that states if periodic events have been started or stopped
278      */
279     private void setPeriodicEventsState(final String host, final Boolean isRunning) {
280         periodicEventsStateCache.put(host, isRunning);
281     }
282
283     /**
284      * This method takes in the latest data entry for an engine, adds it to an existing data set and returns the full
285      * map for that host and engine.
286      *
287      * @param host the engine's host url
288      * @param id the engines id
289      * @param timestamp the timestamp of the latest data entry
290      * @param latestValue the value of the latest data entry
291      * @return a list of {@code Counter} objects for that engine
292      */
293     private List<Counter> getValuesFromCache(final String host, final String id, final long timestamp,
294                     final long latestValue) {
295         SlidingWindowList<Counter> valueList;
296
297         if (!cache.containsKey(host)) {
298             cache.put(host, new HashMap<>());
299         }
300
301         if (cache.get(host).containsKey(id)) {
302             valueList = (SlidingWindowList<Counter>) cache.get(host).get(id);
303         } else {
304             valueList = new SlidingWindowList<>(MAX_CACHED_ENTITIES);
305         }
306         valueList.add(new Counter(timestamp, latestValue));
307
308         cache.get(host).put(id, valueList);
309
310         return valueList;
311     }
312
313
314     /**
315      * Get an engine service facade for sending REST requests. This method is package because it is used by unit test.
316      *
317      * @param hostName the host name of the Apex engine
318      * @param port the port of the Apex engine
319      * @return the engine service facade
320      */
321     protected EngineServiceFacade getEngineServiceFacade(final String hostName, final int port) {
322         return new EngineServiceFacade(hostName, port);
323     }
324
325     /**
326      * A list of values that uses a FIFO sliding window of a fixed size.
327      */
328     public class SlidingWindowList<V> extends LinkedList<V> {
329         private static final long serialVersionUID = -7187277916025957447L;
330
331         private final int maxEntries;
332
333         public SlidingWindowList(final int maxEntries) {
334             this.maxEntries = maxEntries;
335         }
336
337         @Override
338         public boolean add(final V elm) {
339             if (this.size() > (maxEntries - 1)) {
340                 this.removeFirst();
341             }
342             return super.add(elm);
343         }
344
345         private ApexMonitoringRestResource getOuterType() {
346             return ApexMonitoringRestResource.this;
347         }
348
349         @Override
350         public int hashCode() {
351             final int prime = 31;
352             int result = super.hashCode();
353             result = prime * result + getOuterType().hashCode();
354             result = prime * result + maxEntries;
355             return result;
356         }
357
358         @Override
359         public boolean equals(Object obj) {
360             if (this == obj) {
361                 return true;
362             }
363
364             if (!super.equals(obj)) {
365                 return false;
366             }
367
368             if (getClass() != obj.getClass()) {
369                 return false;
370             }
371
372             @SuppressWarnings("unchecked")
373             SlidingWindowList<V> other = (SlidingWindowList<V>) obj;
374             if (!getOuterType().equals(other.getOuterType())) {
375                 return false;
376             }
377
378             return maxEntries == other.maxEntries;
379         }
380     }
381
382     /**
383      * A class used to storing a single data entry for an engine.
384      */
385     public class Counter {
386         private long timestamp;
387         private long value;
388
389         public Counter(final long timestamp, final long value) {
390             this.timestamp = timestamp;
391             this.value = value;
392         }
393
394         public long getTimestamp() {
395             return timestamp;
396         }
397
398         public long getValue() {
399             return value;
400         }
401     }
402 }