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;
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;
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;
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;
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}
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.
61 { MediaType.APPLICATION_JSON })
63 { MediaType.APPLICATION_JSON })
65 public class ApexMonitoringRestResource {
66 // Get a reference to the logger
67 private static final XLogger LOGGER = XLoggerFactory.getXLogger(ApexMonitoringRestResource.class);
69 // Recurring string constants
70 private static final String ERROR_CONNECTING_PREFIX = "Error connecting to Apex Engine Service at ";
72 // Set the maximum number of stored data entries to be stored for each engine
73 private static final int MAX_CACHED_ENTITIES = 50;
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<>();
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<>();
82 * Query the engine service for data.
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
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);
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())
103 final JsonObject responseObject = new JsonObject();
105 // Engine Service data
106 responseObject.addProperty("engine_id", engineServiceFacade.getKey().getId());
107 responseObject.addProperty("model_id",
108 engineServiceFacade.getApexModelKey() != null ? engineServiceFacade.getApexModelKey().getId()
110 responseObject.addProperty("server", hostName);
111 responseObject.addProperty("port", Integer.toString(port));
112 responseObject.addProperty("periodic_events", getPeriodicEventsState(host));
114 // Engine Status data
115 final JsonArray engineStatusList = new JsonArray();
117 for (final AxArtifactKey engineKey : engineServiceFacade.getEngineKeyArray()) {
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));
132 .addProperty("average_policy_duration", gson.toJson(
133 getValuesFromCache(host, engineKey.getId() + "_average_policy_duration",
134 axEngineModel.getTimestamp(),
135 (long) axEngineModel.getStats()
136 .getAverageExecutionTime()),
138 engineStatusList.add(engineStatusObject);
139 } catch (final ApexException e) {
140 LOGGER.warn("Error getting status of engine with ID " + engineKey.getId() + "<br>", e);
143 responseObject.add("status", engineStatusList);
145 // Engine context data
146 final JsonArray engineContextList = new JsonArray();
147 for (final AxArtifactKey engineKey : engineServiceFacade.getEngineKeyArray()) {
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);
156 } catch (final ApexException e) {
157 LOGGER.warn("Error getting runtime information of engine with ID " + engineKey.getId() + "<br>", e);
160 responseObject.add("context", engineContextList);
162 return Response.ok(responseObject.toString(), MediaType.APPLICATION_JSON).build();
166 * Start/Stop and Apex engine.
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
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);
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())
190 final Map<String, String[]> parameterMap = new HashMap<>();
191 parameterMap.put("hostname", new String[]
193 parameterMap.put("port", new String[]
194 { Integer.toString(port) });
195 parameterMap.put("AxArtifactKey#" + engineId, new String[]
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);
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())
212 return Response.ok("{}").build();
216 * Start/Stop Apex engine Periodic Events.
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
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;
233 engineServiceFacade.init();
234 final Map<String, String[]> parameterMap = new HashMap<>();
235 parameterMap.put("hostname", new String[]
237 parameterMap.put("port", new String[]
238 { Integer.toString(port) });
239 parameterMap.put("AxArtifactKey#" + engineId, new String[]
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);
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())
258 return Response.ok("{}").build();
262 * Check if periodic events are running.
264 * @param host the engine's host url
265 * @return a boolean stating if periodic events are running for a given host
267 private Boolean getPeriodicEventsState(final String host) {
268 if (periodicEventsStateCache.containsKey(host)) {
269 return periodicEventsStateCache.get(host);
276 * Sets the state of periodic events for a host.
278 * @param host the engine's host url
279 * @param boolean that states if periodic events have been started or stopped
281 private void setPeriodicEventsState(final String host, final Boolean isRunning) {
282 periodicEventsStateCache.put(host, isRunning);
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.
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
295 private List<Counter> getValuesFromCache(final String host, final String id, final long timestamp,
296 final long latestValue) {
297 SlidingWindowList<Counter> valueList;
299 if (!cache.containsKey(host)) {
300 cache.put(host, new HashMap<String, List<Counter>>());
303 if (cache.get(host).containsKey(id)) {
304 valueList = (SlidingWindowList<Counter>) cache.get(host).get(id);
306 valueList = new SlidingWindowList<>(MAX_CACHED_ENTITIES);
308 valueList.add(new Counter(timestamp, latestValue));
310 cache.get(host).put(id, valueList);
317 * Get an engine service facade for sending REST requests. This method is package because it is used by unit test.
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
323 protected EngineServiceFacade getEngineServiceFacade(final String hostName, final int port) {
324 return new EngineServiceFacade(hostName, port);
328 * A list of values that uses a FIFO sliding window of a fixed size.
330 public class SlidingWindowList<V> extends LinkedList<V> {
331 private static final long serialVersionUID = -7187277916025957447L;
333 private final int maxEntries;
335 public SlidingWindowList(final int maxEntries) {
336 this.maxEntries = maxEntries;
340 public boolean add(final V elm) {
341 if (this.size() > (maxEntries - 1)) {
344 return super.add(elm);
347 private ApexMonitoringRestResource getOuterType() {
348 return ApexMonitoringRestResource.this;
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;
361 public boolean equals(Object obj) {
366 if (!super.equals(obj)) {
370 if (getClass() != obj.getClass()) {
374 @SuppressWarnings("unchecked")
375 SlidingWindowList<V> other = (SlidingWindowList<V>) obj;
376 if (!getOuterType().equals(other.getOuterType())) {
380 return maxEntries == other.maxEntries;
385 * A class used to storing a single data entry for an engine.
387 public class Counter {
388 private long timestamp;
391 public Counter(final long timestamp, final long value) {
392 this.timestamp = timestamp;
396 public long getTimestamp() {
400 public long getValue() {