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