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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.policy.apex.client.monitoring.rest;
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;
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;
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}
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.
58 { MediaType.APPLICATION_JSON })
60 { MediaType.APPLICATION_JSON })
62 public class ApexMonitoringRestResource {
63 // Get a reference to the logger
64 private static final XLogger LOGGER = XLoggerFactory.getXLogger(ApexMonitoringRestResource.class);
66 // Recurring string constants
67 private static final String ERROR_CONNECTING_PREFIX = "Error connecting to Apex Engine Service at ";
69 // Set the maximum number of stored data entries to be stored for each engine
70 private static final int MAX_CACHED_ENTITIES = 50;
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<>();
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<>();
79 * Query the engine service for data.
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
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);
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())
100 final JsonObject responseObject = new JsonObject();
102 // Engine Service data
103 responseObject.addProperty("engine_id", engineServiceFacade.getKey().getId());
104 responseObject.addProperty("model_id",
105 engineServiceFacade.getApexModelKey() != null ? engineServiceFacade.getApexModelKey().getId()
107 responseObject.addProperty("server", hostName);
108 responseObject.addProperty("port", Integer.toString(port));
109 responseObject.addProperty("periodic_events", getPeriodicEventsState(host));
111 // Engine Status data
112 final JsonArray engineStatusList = new JsonArray();
114 for (final AxArtifactKey engineKey : engineServiceFacade.getEngineKeyArray()) {
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));
129 .addProperty("average_policy_duration", gson.toJson(
130 getValuesFromCache(host, engineKey.getId() + "_average_policy_duration",
131 axEngineModel.getTimestamp(),
132 (long) axEngineModel.getStats()
133 .getAverageExecutionTime()),
135 engineStatusList.add(engineStatusObject);
136 } catch (final ApexException e) {
137 LOGGER.warn("Error getting status of engine with ID " + engineKey.getId() + "<br>", e);
140 responseObject.add("status", engineStatusList);
142 // Engine context data
143 final JsonArray engineContextList = new JsonArray();
144 for (final AxArtifactKey engineKey : engineServiceFacade.getEngineKeyArray()) {
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);
153 } catch (final ApexException e) {
154 LOGGER.warn("Error getting runtime information of engine with ID " + engineKey.getId() + "<br>", e);
157 responseObject.add("context", engineContextList);
159 return Response.ok(responseObject.toString(), MediaType.APPLICATION_JSON).build();
163 * Start/Stop and Apex engine.
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
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);
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())
187 final Map<String, String[]> parameterMap = new HashMap<>();
188 parameterMap.put("hostname", new String[]
190 parameterMap.put("port", new String[]
191 { Integer.toString(port) });
192 parameterMap.put("AxArtifactKey#" + engineId, new String[]
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);
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())
209 return Response.ok("{}").build();
213 * Start/Stop Apex engine Periodic Events.
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
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;
230 engineServiceFacade.init();
231 final Map<String, String[]> parameterMap = new HashMap<>();
232 parameterMap.put("hostname", new String[]
234 parameterMap.put("port", new String[]
235 { Integer.toString(port) });
236 parameterMap.put("AxArtifactKey#" + engineId, new String[]
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);
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())
255 return Response.ok("{}").build();
259 * Check if periodic events are running.
261 * @param host the engine's host url
262 * @return a boolean stating if periodic events are running for a given host
264 private Boolean getPeriodicEventsState(final String host) {
265 if (periodicEventsStateCache.containsKey(host)) {
266 return periodicEventsStateCache.get(host);
273 * Sets the state of periodic events for a host.
275 * @param host the engine's host url
276 * @param boolean that states if periodic events have been started or stopped
278 private void setPeriodicEventsState(final String host, final Boolean isRunning) {
279 periodicEventsStateCache.put(host, isRunning);
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.
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
292 private List<Counter> getValuesFromCache(final String host, final String id, final long timestamp,
293 final long latestValue) {
294 SlidingWindowList<Counter> valueList;
296 if (!cache.containsKey(host)) {
297 cache.put(host, new HashMap<String, List<Counter>>());
300 if (cache.get(host).containsKey(id)) {
301 valueList = (SlidingWindowList<Counter>) cache.get(host).get(id);
303 valueList = new SlidingWindowList<>(MAX_CACHED_ENTITIES);
305 valueList.add(new Counter(timestamp, latestValue));
307 cache.get(host).put(id, valueList);
314 * Get an engine service facade for sending REST requests. This method is package because it is used by unit test.
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
320 protected EngineServiceFacade getEngineServiceFacade(final String hostName, final int port) {
321 return new EngineServiceFacade(hostName, port);
325 * A list of values that uses a FIFO sliding window of a fixed size.
327 public class SlidingWindowList<V> extends LinkedList<V> {
328 private static final long serialVersionUID = -7187277916025957447L;
330 private final int maxEntries;
332 public SlidingWindowList(final int maxEntries) {
333 this.maxEntries = maxEntries;
337 public boolean add(final V elm) {
338 if (this.size() > (maxEntries - 1)) {
341 return super.add(elm);
344 private ApexMonitoringRestResource getOuterType() {
345 return ApexMonitoringRestResource.this;
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;
358 public boolean equals(Object obj) {
363 if (!super.equals(obj)) {
367 if (getClass() != obj.getClass()) {
371 @SuppressWarnings("unchecked")
372 SlidingWindowList<V> other = (SlidingWindowList<V>) obj;
373 if (!getOuterType().equals(other.getOuterType())) {
377 return maxEntries == other.maxEntries;
382 * A class used to storing a single data entry for an engine.
384 public class Counter {
385 private long timestamp;
388 public Counter(final long timestamp, final long value) {
389 this.timestamp = timestamp;
393 public long getTimestamp() {
397 public long getValue() {