f8cee7741d3af1918b14bc5c39f05fad276d63e4
[policy/apex-pdp.git] /
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 = new EngineServiceFacade(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 = new EngineServiceFacade(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 (startStop.equals("Start")) {
199                 engineServiceFacade.startEngine(engineKey);
200             } else if (startStop.equals("Stop")) {
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 and Apex engine.
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 = new EngineServiceFacade(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 (startStop.equals("Start")) {
245                 engineServiceFacade.startPerioidicEvents(engineKey, period);
246                 setPeriodicEventsState(host, true);
247             } else if (startStop.equals("Stop")) {
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      * A list of values that uses a FIFO sliding window of a fixed size.
317      */
318     public class SlidingWindowList<V> extends LinkedList<V> {
319         private static final long serialVersionUID = -7187277916025957447L;
320
321         private final int maxEntries;
322
323         public SlidingWindowList(final int maxEntries) {
324             this.maxEntries = maxEntries;
325         }
326
327         @Override
328         public boolean add(final V elm) {
329             if (this.size() > (maxEntries - 1)) {
330                 this.removeFirst();
331             }
332             return super.add(elm);
333         }
334
335     }
336
337     /**
338      * A class used to storing a single data entry for an engine.
339      */
340     public class Counter {
341         private long timestamp;
342         private long value;
343
344         public Counter(final long timestamp, final long value) {
345             this.timestamp = timestamp;
346             this.value = value;
347         }
348
349         public long getTimestamp() {
350             return timestamp;
351         }
352
353         public long getValue() {
354             return value;
355         }
356     }
357 }