2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2016-2018 Ericsson. All rights reserved.
4 * Modifications Copyright (C) 2020 Nordix Foundation.
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.onap.policy.apex.client.monitoring.rest;
24 import com.google.gson.Gson;
25 import com.google.gson.JsonArray;
26 import com.google.gson.JsonObject;
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;
33 import javax.ws.rs.Consumes;
34 import javax.ws.rs.GET;
35 import javax.ws.rs.Path;
36 import javax.ws.rs.Produces;
37 import javax.ws.rs.QueryParam;
38 import javax.ws.rs.core.MediaType;
39 import javax.ws.rs.core.Response;
40 import org.onap.policy.apex.core.deployment.ApexDeploymentException;
41 import org.onap.policy.apex.core.deployment.EngineServiceFacade;
42 import org.onap.policy.apex.model.basicmodel.concepts.ApexException;
43 import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
44 import org.onap.policy.apex.model.enginemodel.concepts.AxEngineModel;
45 import org.slf4j.ext.XLogger;
46 import org.slf4j.ext.XLoggerFactory;
49 * The class represents the root resource exposed at the base URL<br> The url to access this resource would be in the
50 * form {@code <baseURL>/rest/....} <br> For example: a GET request to the following URL
51 * {@code http://localhost:18989/apexservices/rest/?hostName=localhost&port=12345}
53 * <b>Note:</b> An allocated {@code hostName} and {@code port} query parameter must be included in all requests.
54 * Datasets for different {@code hostName} are completely isolated from one another.
59 { MediaType.APPLICATION_JSON })
61 { MediaType.APPLICATION_JSON })
63 public class ApexMonitoringRestResource {
64 // Get a reference to the logger
65 private static final XLogger LOGGER = XLoggerFactory.getXLogger(ApexMonitoringRestResource.class);
67 // Recurring string constants
68 private static final String ERROR_CONNECTING_PREFIX = "Error connecting to Apex Engine Service at ";
70 // Set the maximum number of stored data entries to be stored for each engine
71 private static final int MAX_CACHED_ENTITIES = 50;
73 // Set up a map separated by host and engine for the data
74 private static final HashMap<String, HashMap<String, List<Counter>>> cache = new HashMap<>();
76 // Set up a map separated by host for storing the state of periodic events
77 private static final HashMap<String, Boolean> periodicEventsStateCache = new HashMap<>();
80 * Query the engine service for data.
82 * @param hostName the host name of the engine service to connect to.
83 * @param port the port number of the engine service to connect to.
84 * @return a Response object containing the engines service, status and context data in JSON
87 public Response createSession(@QueryParam("hostName") final String hostName, @QueryParam("port") final int port) {
88 final Gson gson = new Gson();
89 final String host = hostName + ":" + port;
90 final EngineServiceFacade engineServiceFacade = getEngineServiceFacade(hostName, port);
93 engineServiceFacade.init();
94 } catch (final ApexDeploymentException e) {
95 final String errorMessage = ERROR_CONNECTING_PREFIX + host;
96 LOGGER.warn(errorMessage + "<br>", e);
97 return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + e.getMessage())
101 final JsonObject responseObject = new JsonObject();
103 // Engine Service data
104 responseObject.addProperty("engine_id", engineServiceFacade.getKey().getId());
105 responseObject.addProperty("model_id",
106 engineServiceFacade.getApexModelKey() != null ? engineServiceFacade.getApexModelKey().getId()
108 responseObject.addProperty("server", hostName);
109 responseObject.addProperty("port", Integer.toString(port));
110 responseObject.addProperty("periodic_events", getPeriodicEventsState(host));
112 // Engine Status data
113 final JsonArray engineStatusList = new JsonArray();
115 for (final AxArtifactKey engineKey : engineServiceFacade.getEngineKeyArray()) {
117 final JsonObject engineStatusObject = new JsonObject();
118 final AxEngineModel axEngineModel = engineServiceFacade.getEngineStatus(engineKey);
119 engineStatusObject.addProperty("timestamp", axEngineModel.getTimeStampString());
120 engineStatusObject.addProperty("id", engineKey.getId());
121 engineStatusObject.addProperty("status", axEngineModel.getState().toString());
122 engineStatusObject.addProperty("last_message", axEngineModel.getStats().getTimeStampString());
123 engineStatusObject.addProperty("up_time", axEngineModel.getStats().getUpTime() / 1000L);
124 engineStatusObject.addProperty("policy_executions", axEngineModel.getStats().getEventCount());
125 engineStatusObject.addProperty("last_policy_duration",
126 gson.toJson(getValuesFromCache(host, engineKey.getId() + "_last_policy_duration",
127 axEngineModel.getTimestamp(),
128 axEngineModel.getStats().getLastExecutionTime()), List.class));
130 .addProperty("average_policy_duration", gson.toJson(
131 getValuesFromCache(host, engineKey.getId() + "_average_policy_duration",
132 axEngineModel.getTimestamp(),
133 (long) axEngineModel.getStats()
134 .getAverageExecutionTime()),
136 engineStatusList.add(engineStatusObject);
137 } catch (final ApexException e) {
138 LOGGER.warn("Error getting status of engine with ID " + engineKey.getId() + "<br>", e);
141 responseObject.add("status", engineStatusList);
143 // Engine context data
144 final JsonArray engineContextList = new JsonArray();
145 for (final AxArtifactKey engineKey : engineServiceFacade.getEngineKeyArray()) {
147 final String engineInfo = engineServiceFacade.getEngineInfo(engineKey);
148 if (engineInfo != null && !engineInfo.trim().isEmpty()) {
149 final JsonObject engineContextObject = new JsonObject();
150 engineContextObject.addProperty("id", engineKey.getId());
151 engineContextObject.addProperty("engine_info", engineInfo);
152 engineContextList.add(engineContextObject);
154 } catch (final ApexException e) {
155 LOGGER.warn("Error getting runtime information of engine with ID " + engineKey.getId() + "<br>", e);
158 responseObject.add("context", engineContextList);
160 return Response.ok(responseObject.toString(), MediaType.APPLICATION_JSON).build();
164 * Start/Stop and Apex engine.
166 * @param hostName the host name of the engine service to connect to.
167 * @param port the port number of the engine service to connect to.
168 * @param engineId the id of the engine to be started/stopped.
169 * @param startStop the parameter to start/stop the engine. Expects either "Start" or "Stop"
170 * @return a Response object of type 200
174 public Response startStop(@QueryParam("hostName") final String hostName, @QueryParam("port") final int port,
175 @QueryParam("engineId") final String engineId, @QueryParam("startstop") final String startStop) {
176 final EngineServiceFacade engineServiceFacade = getEngineServiceFacade(hostName, port);
179 engineServiceFacade.init();
180 } catch (final ApexDeploymentException e) {
181 final String errorMessage = ERROR_CONNECTING_PREFIX + hostName + ":" + port;
182 LOGGER.warn(errorMessage + "<br>", e);
183 return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + e.getMessage())
188 final Map<String, String[]> parameterMap = new HashMap<>();
189 parameterMap.put("hostname", new String[]
191 parameterMap.put("port", new String[]
192 { Integer.toString(port) });
193 parameterMap.put("AxArtifactKey#" + engineId, new String[]
195 final AxArtifactKey engineKey = ParameterCheck.getEngineKey(parameterMap);
196 if ("Start".equals(startStop)) {
197 engineServiceFacade.startEngine(engineKey);
198 } else if ("Stop".equals(startStop)) {
199 engineServiceFacade.stopEngine(engineKey);
201 } catch (final Exception e) {
202 final String errorMessage = "Error calling " + startStop + " on Apex Engine: " + engineId;
203 LOGGER.warn(errorMessage + "<br>", e);
204 final StringWriter sw = new StringWriter();
205 e.printStackTrace(new PrintWriter(sw));
206 return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + sw.toString())
210 return Response.ok("{}").build();
214 * Start/Stop Apex engine Periodic Events.
216 * @param hostName the host name of the engine service to connect to.
217 * @param port the port number of the engine service to connect to.
218 * @param engineId the id of the engine to be started/stopped.
219 * @param startStop the parameter to start/stop the engine. Expects either "Start" or "Stop"
220 * @param period the time between each event in milliseconds
221 * @return a Response object of type 200
224 @Path("periodiceventstartstop/")
225 public Response periodiceventStartStop(@QueryParam("hostName") final String hostName,
226 @QueryParam("port") final int port, @QueryParam("engineId") final String engineId,
227 @QueryParam("startstop") final String startStop, @QueryParam("period") final long period) {
228 final EngineServiceFacade engineServiceFacade = getEngineServiceFacade(hostName, port);
229 final String host = hostName + ":" + port;
231 engineServiceFacade.init();
232 final Map<String, String[]> parameterMap = new HashMap<>();
233 parameterMap.put("hostname", new String[]
235 parameterMap.put("port", new String[]
236 { Integer.toString(port) });
237 parameterMap.put("AxArtifactKey#" + engineId, new String[]
239 parameterMap.put("period", new String[]
240 { Long.toString(period) });
241 final AxArtifactKey engineKey = ParameterCheck.getEngineKey(parameterMap);
242 if ("Start".equals(startStop)) {
243 engineServiceFacade.startPerioidicEvents(engineKey, period);
244 setPeriodicEventsState(host, true);
245 } else if ("Stop".equals(startStop)) {
246 engineServiceFacade.stopPerioidicEvents(engineKey);
247 setPeriodicEventsState(host, false);
249 } catch (final ApexDeploymentException e) {
250 final String errorMessage = ERROR_CONNECTING_PREFIX + host;
251 LOGGER.warn(errorMessage + "<br>", e);
252 return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + e.getMessage())
256 return Response.ok("{}").build();
260 * Check if periodic events are running.
262 * @param host the engine's host url
263 * @return a boolean stating if periodic events are running for a given host
265 private Boolean getPeriodicEventsState(final String host) {
266 if (periodicEventsStateCache.containsKey(host)) {
267 return periodicEventsStateCache.get(host);
274 * Sets the state of periodic events for a host.
276 * @param host the engine's host url
277 * @param boolean that states if periodic events have been started or stopped
279 private void setPeriodicEventsState(final String host, final Boolean isRunning) {
280 periodicEventsStateCache.put(host, isRunning);
284 * This method takes in the latest data entry for an engine, adds it to an existing data set and returns the full
285 * map for that host and engine.
287 * @param host the engine's host url
288 * @param id the engines id
289 * @param timestamp the timestamp of the latest data entry
290 * @param latestValue the value of the latest data entry
291 * @return a list of {@code Counter} objects for that engine
293 private List<Counter> getValuesFromCache(final String host, final String id, final long timestamp,
294 final long latestValue) {
295 SlidingWindowList<Counter> valueList;
297 if (!cache.containsKey(host)) {
298 cache.put(host, new HashMap<>());
301 if (cache.get(host).containsKey(id)) {
302 valueList = (SlidingWindowList<Counter>) cache.get(host).get(id);
304 valueList = new SlidingWindowList<>(MAX_CACHED_ENTITIES);
306 valueList.add(new Counter(timestamp, latestValue));
308 cache.get(host).put(id, valueList);
315 * Get an engine service facade for sending REST requests. This method is package because it is used by unit test.
317 * @param hostName the host name of the Apex engine
318 * @param port the port of the Apex engine
319 * @return the engine service facade
321 protected EngineServiceFacade getEngineServiceFacade(final String hostName, final int port) {
322 return new EngineServiceFacade(hostName, port);
326 * A list of values that uses a FIFO sliding window of a fixed size.
328 public class SlidingWindowList<V> extends LinkedList<V> {
329 private static final long serialVersionUID = -7187277916025957447L;
331 private final int maxEntries;
333 public SlidingWindowList(final int maxEntries) {
334 this.maxEntries = maxEntries;
338 public boolean add(final V elm) {
339 if (this.size() > (maxEntries - 1)) {
342 return super.add(elm);
345 private ApexMonitoringRestResource getOuterType() {
346 return ApexMonitoringRestResource.this;
350 public int hashCode() {
351 final int prime = 31;
352 int result = super.hashCode();
353 result = prime * result + getOuterType().hashCode();
354 result = prime * result + maxEntries;
359 public boolean equals(Object obj) {
364 if (!super.equals(obj)) {
368 if (getClass() != obj.getClass()) {
372 @SuppressWarnings("unchecked")
373 SlidingWindowList<V> other = (SlidingWindowList<V>) obj;
374 if (!getOuterType().equals(other.getOuterType())) {
378 return maxEntries == other.maxEntries;
383 * A class used to storing a single data entry for an engine.
385 public class Counter {
386 private long timestamp;
389 public Counter(final long timestamp, final long value) {
390 this.timestamp = timestamp;
394 public long getTimestamp() {
398 public long getValue() {