60dfc81cd307b6b232144c87e7940d92b9b8d176
[policy/gui.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2020-2021 Nordix Foundation.
4  *  Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
5  *  Modifications Copyright (C) 2021 Bell Canada. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  *
19  * SPDX-License-Identifier: Apache-2.0
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.gui.pdp.monitoring.rest;
24
25 import com.google.gson.Gson;
26 import com.google.gson.GsonBuilder;
27 import com.google.gson.JsonDeserializer;
28 import com.google.gson.reflect.TypeToken;
29 import java.time.Instant;
30 import java.util.ArrayList;
31 import java.util.Date;
32 import java.util.HashMap;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Objects;
37 import javax.ws.rs.Consumes;
38 import javax.ws.rs.GET;
39 import javax.ws.rs.Path;
40 import javax.ws.rs.Produces;
41 import javax.ws.rs.QueryParam;
42 import javax.ws.rs.core.MediaType;
43 import javax.ws.rs.core.Response;
44 import lombok.AllArgsConstructor;
45 import lombok.EqualsAndHashCode;
46 import lombok.Getter;
47 import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams;
48 import org.onap.policy.common.endpoints.http.client.HttpClient;
49 import org.onap.policy.common.endpoints.http.client.HttpClientConfigException;
50 import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
51 import org.onap.policy.common.utils.coder.CoderException;
52 import org.onap.policy.common.utils.coder.StandardCoder;
53 import org.onap.policy.models.pdp.concepts.PdpEngineWorkerStatistics;
54 import org.onap.policy.models.pdp.concepts.PdpGroups;
55 import org.onap.policy.models.pdp.concepts.PdpStatistics;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 /**
60  * The class represents the root resource exposed at the base URL<br>
61  * The url to access this resource would be in the form {@code <baseURL>/rest/....} <br>
62  * For example: a GET request to the following URL
63  * {@code http://localhost:18989/papservices/rest/?hostName=localhost&port=12345}
64  *
65  * <b>Note:</b> An allocated {@code hostName} and {@code port} query parameter must be included in
66  * all requests. Datasets for different {@code hostName} are completely isolated from one another.
67  *
68  * @author Yehui Wang (yehui.wang@est.tech)
69  */
70 @Path("monitoring/")
71 @Produces({MediaType.APPLICATION_JSON})
72 @Consumes({MediaType.APPLICATION_JSON})
73 public class PdpMonitoringRestResource {
74     // Get a reference to the logger
75     private static final Logger LOGGER = LoggerFactory.getLogger(PdpMonitoringRestResource.class);
76     // Set up a map separated by host and engine for the data
77     private static final Map<String, HashMap<String, List<Counter>>> cache = new HashMap<>();
78
79     // Set the maximum number of stored data entries to be stored for each engine
80     private static final int MAX_CACHED_ENTITIES = 50;
81
82     private static Gson gson = new GsonBuilder()
83         .registerTypeAdapter(Instant.class, (JsonDeserializer<Instant>) (jsonElement, type, jsonDeserializationContext)
84             -> Instant.parse(jsonElement.getAsJsonPrimitive().getAsString()))
85         .create();
86
87     /**
88      * Query Pdps.
89      *
90      * @param useHttps use Http or not
91      * @param hostname hostname the host name of the engine service to connect to.
92      * @param port port the port number of the engine service to connect to.
93      * @param username user name
94      * @param password password
95      * @return a Response object containing Pdps in JSON
96      * @throws HttpClientConfigException exception
97      */
98     @GET
99     public Response getPdps(@QueryParam("useHttps") final String useHttps,
100             @QueryParam("hostname") final String hostname, @QueryParam("port") final int port,
101             @QueryParam("username") final String username, @QueryParam("password") final String password)
102             throws HttpClientConfigException {
103
104         return Response
105                 .ok(getHttpClient(useHttps, hostname, port, username, password, "policy/pap/v1/pdps").get().getEntity(),
106                         MediaType.APPLICATION_JSON)
107                 .build();
108     }
109
110     /**
111      * Query Pdp statistics.
112      *
113      * @param useHttps use Http or not
114      * @param hostname the host name of the engine service to connect to.
115      * @param port the port number of the engine service to connect to.
116      * @param username user name
117      * @param password password
118      * @param id PdpGroupName/PdpSubGroup/PdpIntanceID
119      * @return a Response object containing the Pdp status and context data in JSON
120      * @throws HttpClientConfigException exception
121      * @throws CoderException Coder exception
122      */
123     @GET
124     @Path("statistics/")
125     public Response getStatistics(@QueryParam("useHttps") final String useHttps,
126             @QueryParam("hostname") final String hostname, @QueryParam("port") final int port,
127             @QueryParam("username") final String username, @QueryParam("password") final String password,
128             @QueryParam("id") final String id) throws HttpClientConfigException, CoderException {
129
130         var pdpGroups = getHttpClient(useHttps, hostname, port, username, password, "policy/pap/v1/pdps").get()
131                 .readEntity(PdpGroups.class);
132         String groupName;
133         String subGroup;
134         String instanceId;
135         String[] idArray = id.split("/");
136         if (idArray.length == 3) {
137             groupName = idArray[0];
138             subGroup = idArray[1];
139             instanceId = idArray[2];
140         } else {
141             throw new IllegalArgumentException("Cannot parse groupName, subGroup and instanceId from " + id);
142         }
143
144         var pdp = pdpGroups.getGroups().stream().filter(group -> group.getName().equals(groupName))
145                 .flatMap(group -> group.getPdpSubgroups().stream().filter(sub -> sub.getPdpType().equals(subGroup)))
146                 .flatMap(sub -> sub.getPdpInstances().stream()
147                         .filter(instance -> instance.getInstanceId().equals(instanceId)))
148                 .filter(Objects::nonNull).findFirst().orElseThrow();
149
150         final var responseObject = new StatisticsResponse();
151
152         // Engine Service data
153         responseObject.setEngineId(pdp.getInstanceId());
154         responseObject.setServer(hostname);
155         responseObject.setPort(Integer.toString(port));
156         responseObject.setHealthStatus(pdp.getHealthy().name());
157         responseObject.setPdpState(pdp.getPdpState().name());
158
159         String statisticsEntity = getHttpClient(useHttps, hostname, port, username, password,
160                 "policy/pap/v1/pdps/statistics/" + id + "?recordCount=1").get().readEntity(String.class);
161         Map<String, Map<String, List<PdpStatistics>>> pdpStats = gson.fromJson(statisticsEntity,
162                 new TypeToken<Map<String, Map<String, List<PdpStatistics>>>>() {}.getType());
163
164         final List<EngineStatus> engineStatusList = new ArrayList<>();
165
166         if (!pdpStats.isEmpty()) {
167             var pdpStatistics = pdpStats.get(groupName).get(subGroup).get(0);
168             responseObject.setTimeStamp(pdpStatistics.getTimeStamp().toString());
169             responseObject.setPolicyDeployCount(pdpStatistics.getPolicyDeployCount());
170             responseObject.setPolicyDeploySuccessCount(pdpStatistics.getPolicyDeploySuccessCount());
171             responseObject.setPolicyDeployFailCount(pdpStatistics.getPolicyDeployFailCount());
172             responseObject.setPolicyExecutedCount(pdpStatistics.getPolicyExecutedCount());
173             responseObject.setPolicyExecutedSuccessCount(pdpStatistics.getPolicyExecutedSuccessCount());
174             responseObject.setPolicyExecutedFailCount(pdpStatistics.getPolicyExecutedFailCount());
175
176             // Engine Status data
177             for (final PdpEngineWorkerStatistics engineStats : pdpStatistics.getEngineStats()) {
178                 try {
179                     final var engineStatusObject = new EngineStatus();
180                     engineStatusObject.setTimestamp(pdpStatistics.getTimeStamp().toString());
181                     engineStatusObject.setId(engineStats.getEngineId());
182                     engineStatusObject.setStatus(engineStats.getEngineWorkerState().name());
183                     engineStatusObject.setLastMessage(new Date(engineStats.getEngineTimeStamp()).toString());
184                     engineStatusObject.setUpTime(engineStats.getUpTime());
185                     engineStatusObject.setPolicyExecutions(engineStats.getEventCount());
186                     engineStatusObject.setLastPolicyDuration(gson.toJson(
187                             getValuesFromCache(id, engineStats.getEngineId() + "_last_policy_duration",
188                                     pdpStatistics.getTimeStamp().getEpochSecond(), engineStats.getLastExecutionTime()),
189                             List.class));
190                     engineStatusObject.setAveragePolicyDuration(
191                             gson.toJson(getValuesFromCache(id, engineStats.getEngineId() + "_average_policy_duration",
192                                     pdpStatistics.getTimeStamp().getEpochSecond(),
193                                     (long) engineStats.getAverageExecutionTime()), List.class));
194                     engineStatusList.add(engineStatusObject);
195                 } catch (final RuntimeException e) {
196                     LOGGER.warn("Error getting status of engine with ID " + engineStats.getEngineId() + "<br>", e);
197                 }
198             }
199         } else {
200             responseObject.setTimeStamp("N/A");
201             responseObject.setPolicyDeployCount("N/A");
202             responseObject.setPolicyDeploySuccessCount("N/A");
203             responseObject.setPolicyDeployFailCount("N/A");
204             responseObject.setPolicyExecutedCount("N/A");
205             responseObject.setPolicyExecutedSuccessCount("N/A");
206             responseObject.setPolicyExecutedFailCount("N/A");
207         }
208
209         responseObject.setStatus(engineStatusList);
210         return Response.ok(new StandardCoder().encode(responseObject), MediaType.APPLICATION_JSON).build();
211     }
212
213     private HttpClient getHttpClient(String useHttps, String hostname, int port, String username, String password,
214             String basePath) throws HttpClientConfigException {
215         var busParams = new BusTopicParams();
216         busParams.setClientName("pdp-monitoring");
217         busParams.setHostname(hostname);
218         busParams.setManaged(false);
219         busParams.setPassword(password);
220         busParams.setPort(port);
221         busParams.setUseHttps(useHttps.equals("https"));
222         busParams.setUserName(username);
223         busParams.setBasePath(basePath);
224         return HttpClientFactoryInstance.getClientFactory().build(busParams);
225     }
226
227     /**
228      * This method takes in the latest data entry for an engine, adds it to an existing data set and
229      * returns the full map for that host and engine.
230      *
231      * @param uri the pdp uri
232      * @param id the engines id
233      * @param timestamp the timestamp of the latest data entry
234      * @param latestValue the value of the latest data entry
235      * @return a list of {@code Counter} objects for that engine
236      */
237     private synchronized List<Counter> getValuesFromCache(final String uri, final String id, final long timestamp,
238             final long latestValue) {
239
240         Map<String, List<Counter>> engineStatus = cache.computeIfAbsent(uri, k -> new HashMap<>());
241
242         List<Counter> valueList = engineStatus.computeIfAbsent(id, k -> new SlidingWindowList<>(MAX_CACHED_ENTITIES));
243
244         valueList.add(new Counter(timestamp, latestValue));
245
246         return valueList;
247     }
248
249     /**
250      * A list of values that uses a FIFO sliding window of a fixed size.
251      */
252     @EqualsAndHashCode(callSuper = true)
253     @AllArgsConstructor
254     public class SlidingWindowList<V> extends LinkedList<V> {
255         private static final long serialVersionUID = -7187277916025957447L;
256
257         private final int maxEntries;
258
259         @Override
260         public boolean add(final V elm) {
261             if (this.size() > (maxEntries - 1)) {
262                 this.removeFirst();
263             }
264             return super.add(elm);
265         }
266     }
267
268     /**
269      * A class used to storing a single data entry for an engine.
270      */
271     @Getter
272     @AllArgsConstructor
273     public class Counter {
274         private final long timestamp;
275         private final long value;
276     }
277
278 }