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