85465a14b4ecf5f8044e6109b8c3708a49957632
[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>
52  * The url to access this resource would be in the form {@code <baseURL>/rest/....} <br>
53  * For example: a GET request to the following URL
54  * {@code http://localhost:18989/apexservices/rest/?hostName=localhost&port=12345}
55  *
56  * <b>Note:</b> An allocated {@code hostName} and {@code port} query parameter must be included in all requests.
57  * Datasets for different {@code hostName} are completely isolated from one another.
58  *
59  */
60 @Path("monitoring/")
61 @Produces({ MediaType.APPLICATION_JSON })
62 @Consumes({ MediaType.APPLICATION_JSON })
63
64 public class ApexMonitoringRestResource {
65     // Get a reference to the logger
66     private static final XLogger LOGGER = XLoggerFactory.getXLogger(ApexMonitoringRestResource.class);
67
68     // Set the maximum number of stored data entries to be stored for each engine
69     private static final int maxCachedEntries = 50;
70
71     // Set up a map separated by host and engine for the data
72     private static final HashMap<String, HashMap<String, List<Counter>>> cache =
73             new HashMap<String, HashMap<String, List<Counter>>>();
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<String, Boolean>();
77
78     /**
79      * Constructor, a new resource director is created for each request.
80      */
81     public ApexMonitoringRestResource() {}
82
83     /**
84      * Query the engine service for data.
85      *
86      * @param hostName the host name of the engine service to connect to.
87      * @param port the port number of the engine service to connect to.
88      * @return a Response object containing the engines service, status and context data in JSON
89      */
90     @GET
91     public Response createSession(@QueryParam("hostName") final String hostName, @QueryParam("port") final int port) {
92         final Gson gson = new Gson();
93         final String host = hostName + ":" + port;
94         final EngineServiceFacade engineServiceFacade = new EngineServiceFacade(hostName, port);
95
96         try {
97             engineServiceFacade.init();
98         } catch (final ApexDeploymentException e) {
99             final String errorMessage = "Error connecting to Apex Engine Service at " + host;
100             LOGGER.warn(errorMessage + "<br>", e);
101             return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + e.getMessage())
102                     .build();
103         }
104
105         final JsonObject responseObject = new JsonObject();
106
107         // Engine Service data
108         responseObject.addProperty("engine_id", engineServiceFacade.getKey().getId());
109         responseObject.addProperty("model_id",
110                 engineServiceFacade.getApexModelKey() != null ? engineServiceFacade.getApexModelKey().getId()
111                         : "Not Set");
112         responseObject.addProperty("server", hostName);
113         responseObject.addProperty("port", Integer.toString(port));
114         responseObject.addProperty("periodic_events", getPeriodicEventsState(host));
115
116         // Engine Status data
117         final JsonArray engineStatusList = new JsonArray();
118
119         for (final AxArtifactKey engineKey : engineServiceFacade.getEngineKeyArray()) {
120             try {
121                 final JsonObject engineStatusObject = new JsonObject();
122                 final AxEngineModel axEngineModel = engineServiceFacade.getEngineStatus(engineKey);
123                 engineStatusObject.addProperty("timestamp", axEngineModel.getTimeStampString());
124                 engineStatusObject.addProperty("id", engineKey.getId());
125                 engineStatusObject.addProperty("status", axEngineModel.getState().toString());
126                 engineStatusObject.addProperty("last_message", axEngineModel.getStats().getTimeStampString());
127                 engineStatusObject.addProperty("up_time", axEngineModel.getStats().getUpTime() / 1000L);
128                 engineStatusObject.addProperty("policy_executions", axEngineModel.getStats().getEventCount());
129                 engineStatusObject.addProperty("last_policy_duration",
130                         gson.toJson(
131                                 getValuesFromCache(host, engineKey.getId() + "_last_policy_duration",
132                                         axEngineModel.getTimestamp(), axEngineModel.getStats().getLastExecutionTime()),
133                                 List.class));
134                 engineStatusObject.addProperty("average_policy_duration",
135                         gson.toJson(getValuesFromCache(host, engineKey.getId() + "_average_policy_duration",
136                                 axEngineModel.getTimestamp(),
137                                 (long) axEngineModel.getStats().getAverageExecutionTime()), 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 to Apex Engine Service at " + 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<String, String[]>();
191             parameterMap.put("hostname", new String[] { hostName });
192             parameterMap.put("port", new String[] { Integer.toString(port) });
193             parameterMap.put("AxArtifactKey#" + engineId, new String[] { startStop });
194             final AxArtifactKey engineKey = ParameterCheck.getEngineKey(parameterMap);
195             if (startStop.equals("Start")) {
196                 engineServiceFacade.startEngine(engineKey);
197             } else if (startStop.equals("Stop")) {
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 and Apex engine.
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 = new EngineServiceFacade(hostName, port);
228         final String host = hostName + ":" + port;
229         try {
230             engineServiceFacade.init();
231             final Map<String, String[]> parameterMap = new HashMap<String, String[]>();
232             parameterMap.put("hostname", new String[] { hostName });
233             parameterMap.put("port", new String[] { Integer.toString(port) });
234             parameterMap.put("AxArtifactKey#" + engineId, new String[] { startStop });
235             parameterMap.put("period", new String[] { Long.toString(period) });
236             final AxArtifactKey engineKey = ParameterCheck.getEngineKey(parameterMap);
237             if (startStop.equals("Start")) {
238                 engineServiceFacade.startPerioidicEvents(engineKey, period);
239                 setPeriodicEventsState(host, true);
240             } else if (startStop.equals("Stop")) {
241                 engineServiceFacade.stopPerioidicEvents(engineKey);
242                 setPeriodicEventsState(host, false);
243             }
244         } catch (final ApexDeploymentException e) {
245             final String errorMessage = "Error connecting to Apex Engine Service at " + host;
246             LOGGER.warn(errorMessage + "<br>", e);
247             return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + e.getMessage())
248                     .build();
249         }
250
251         return Response.ok("{}").build();
252     }
253
254     /**
255      * Check if periodic events are running.
256      *
257      * @param host the engine's host url
258      * @return a boolean stating if periodic events are running for a given host
259      */
260     private Boolean getPeriodicEventsState(final String host) {
261         return periodicEventsStateCache.containsKey(host) ? periodicEventsStateCache.get(host) : false;
262     }
263
264     /**
265      * Sets the state of periodic events for a host.
266      *
267      * @param host the engine's host url
268      * @param boolean that states if periodic events have been started or stopped
269      */
270     private void setPeriodicEventsState(final String host, final Boolean isRunning) {
271         periodicEventsStateCache.put(host, isRunning);
272     }
273
274     /**
275      * This method takes in the latest data entry for an engine, adds it to an existing data set and returns the full
276      * map for that host and engine.
277      *
278      * @param host the engine's host url
279      * @param id the engines id
280      * @param timestamp the timestamp of the latest data entry
281      * @param latestValue the value of the latest data entry
282      * @return a list of {@code Counter} objects for that engine
283      */
284     private List<Counter> getValuesFromCache(final String host, final String id, final long timestamp,
285             final long latestValue) {
286         SlidingWindowList<Counter> valueList;
287
288         if (!cache.containsKey(host)) {
289             cache.put(host, new HashMap<String, List<Counter>>());
290         }
291
292         if (cache.get(host).containsKey(id)) {
293             valueList = (SlidingWindowList<Counter>) cache.get(host).get(id);
294         } else {
295             valueList = new SlidingWindowList<Counter>(maxCachedEntries);
296         }
297         valueList.add(new Counter(timestamp, latestValue));
298
299         cache.get(host).put(id, valueList);
300
301         return valueList;
302     }
303
304     /**
305      * A list of values that uses a FIFO sliding window of a fixed size.
306      */
307     public class SlidingWindowList<V> extends LinkedList<V> {
308         private static final long serialVersionUID = -7187277916025957447L;
309
310         private final int maxEntries;
311
312         public SlidingWindowList(final int maxEntries) {
313             this.maxEntries = maxEntries;
314         }
315
316         @Override
317         public boolean add(final V elm) {
318             if (this.size() > (maxEntries - 1)) {
319                 this.removeFirst();
320             }
321             return super.add(elm);
322         }
323
324     }
325
326     /**
327      * A class used to storing a single data entry for an engine.
328      */
329     public class Counter {
330         private long timestamp;
331         private long value;
332
333         public Counter(final long timestamp, final long value) {
334             this.timestamp = timestamp;
335             this.value = value;
336         }
337
338         public long getTimestamp() {
339             return timestamp;
340         }
341
342         public long getValue() {
343             return value;
344         }
345     }
346 }