App metrics visualization manage
[portal/ric-dashboard.git] / dashboard / webapp-backend / src / main / java / org / oransc / ric / portal / dashboard / AppStatsManager.java
1 /*-
2  * ========================LICENSE_START=================================
3  * O-RAN-SC
4  * %%
5  * Copyright (C) 2020 AT&T Intellectual Property
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  * ========================LICENSE_END===================================
19  */
20 package org.oransc.ric.portal.dashboard;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.lang.invoke.MethodHandles;
25 import java.nio.file.Files;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.ListIterator;
29
30 import javax.servlet.http.HttpServletResponse;
31
32 import org.onap.portalsdk.core.onboarding.exception.PortalAPIException;
33 import org.onap.portalsdk.core.restful.domain.EcompUser;
34 import org.oransc.ric.portal.dashboard.model.StatsDetailsTransport;
35 import org.oransc.ric.portal.dashboard.exception.StatsManagerException;
36 import org.oransc.ric.portal.dashboard.model.IDashboardResponse;
37 import org.oransc.ric.portal.dashboard.model.AppStats;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40 import org.springframework.stereotype.Component;
41 import org.springframework.stereotype.Service;
42
43 import com.fasterxml.jackson.core.type.TypeReference;
44 import com.fasterxml.jackson.databind.ObjectMapper;
45
46 /**
47  * Provides simple xApp stats-management services.
48  * 
49  * This first implementation serializes xApp stat details to a file.
50  * 
51  * Migrate to a database someday?
52  */
53
54 public class AppStatsManager {
55
56         private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
57
58         // This default value is only useful for development and testing.
59         public static final String STATS_FILE_PATH = "app-stats.json";
60
61         private final File statsFile;
62         private final List<AppStats> stats;
63         private int appMaxId = -1;
64
65         /**
66          * Development/test-only constructor that uses default file path.
67          * 
68          * @param clear If true, start empty and remove any existing file.
69          * 
70          * @throws IOException On file error
71          */
72         public AppStatsManager(boolean clear) throws IOException {
73                 this(STATS_FILE_PATH);
74                 if (clear) {
75                         logger.debug("ctor: removing file {}", statsFile.getAbsolutePath());
76                         File f = new File(AppStatsManager.STATS_FILE_PATH);
77                         if (f.exists())
78                                 Files.delete(f.toPath());
79                         stats.clear();
80                 }
81         }
82
83         /**
84          * Constructor that accepts a file path
85          * 
86          * @param statsFilePath File path
87          * @throws IOException If file cannot be read
88          */
89         public AppStatsManager(final String statsFilePath) throws IOException {
90                 logger.debug("ctor: statsfile {}", statsFilePath);
91                 if (statsFilePath == null)
92                         throw new IllegalArgumentException("Missing or empty stats file property");
93                 statsFile = new File(statsFilePath);
94                 logger.debug("ctor: managing stats in file {}", statsFile.getAbsolutePath());
95                 if (statsFile.exists()) {
96                         final ObjectMapper mapper = new ObjectMapper();
97                         stats = mapper.readValue(statsFile, new TypeReference<List<AppStats>>() {
98                         });
99                         for (AppStats st: stats) {
100                                 if (st.getStatsDetails().getAppId()>appMaxId) 
101                                         appMaxId = st.getStatsDetails().getAppId();
102                         }
103                 } else {
104                         stats = new ArrayList<>();
105                 }
106         }
107
108         /**
109          * Gets the current app metric stats.
110          * 
111          * @return List of App stat objects, possibly empty
112          */
113         public List<AppStats> getStats() {
114                 return this.stats;
115         }
116
117         /**
118          * Gets the current app metric stats by instance key.
119          * 
120          * @param instanceKey Desired instance key
121          * @return List of App stat objects by instance key, possibly empty
122          */
123         public List<AppStats> getStatsByInstance(String instanceKey) {
124                 List<AppStats> statsByInstance = new ArrayList<AppStats>();
125                 for (AppStats st : this.stats) {
126                         if (st.getInstanceKey().equals(instanceKey)) {
127                                 logger.debug("getStatsByInstance: match on instance key {}", instanceKey);
128                                 statsByInstance.add(st);
129                         }
130                 }
131                 return statsByInstance;
132         }
133
134         /**
135          * Gets the stats with the specified app Id and instance key
136          * 
137          * @param appId       Desired app Id
138          * @param instanceKey Desired instance key
139          * @return Stats object; null if Id is not known
140          */
141         public AppStats getStatsById(String instanceKey, int appId) {
142
143                 for (AppStats st : this.stats) {
144                         if (st.getInstanceKey().equals(instanceKey) && st.getStatsDetails().getAppId() == appId) {
145                                 logger.debug("getStatsById: match on app id {} with instance key {}", appId, instanceKey);
146                                 return st;
147                         }
148                 }
149                 logger.debug("getStatsById: no match on app id with instance key {}{}", appId, instanceKey);
150                 return null;
151
152         }
153
154         private void saveStats() throws IOException {
155                 final ObjectMapper mapper = new ObjectMapper();
156                 mapper.writeValue(statsFile, stats);
157         }
158
159         /*
160          * Allow at most one thread to create a stats at one time.
161          * Before creating new stat, checks for composite key (appname,url) uniqueness for an instance key
162          */
163         public synchronized AppStats createStats(String instanceKey, StatsDetailsTransport statsSetupRequest)
164                         throws StatsManagerException, IOException {
165                 logger.debug("createStats: appId {}, instanceKey {}", statsSetupRequest.getAppId(), instanceKey);
166
167                 for (AppStats st : stats) {
168                         if (st.getInstanceKey().equals(instanceKey)
169                                         && st.getStatsDetails().getAppName().equals(statsSetupRequest.getAppName())
170                                         && st.getStatsDetails().getMetricUrl().equals(statsSetupRequest.getMetricUrl())) {
171                                 String msg = "App exists with name " + statsSetupRequest.getAppName() + " and url "+statsSetupRequest.getMetricUrl()+ " on instance key " + instanceKey;
172                                 logger.warn(msg);
173                                 throw new StatsManagerException(msg);
174                         }
175                 }
176
177                 AppStats newAppStat = null;
178                 //Assigns appId to be 1 more than the largest value stored in memory
179                 appMaxId = appMaxId+1;
180                 newAppStat = new AppStats(instanceKey,
181                                 new StatsDetailsTransport(appMaxId, statsSetupRequest.getAppName(), statsSetupRequest.getMetricUrl()));
182                 stats.add(newAppStat);
183                 saveStats();
184                 return newAppStat;
185         }
186
187         /*
188          * Allow at most one thread to modify a stats at one time. We still have
189          * last-edit-wins of course.
190          */
191         public synchronized void updateStats(String instanceKey, StatsDetailsTransport statsSetupRequest)
192                         throws StatsManagerException, IOException {
193                 logger.debug("updateStats: appId {}, instanceKey {}", statsSetupRequest.getAppId(), instanceKey);
194                 boolean editStatsObjectFound = false;
195
196                 for (AppStats st : stats) {
197                         if (st.getInstanceKey().equals(instanceKey)
198                                         && st.getStatsDetails().getAppId() == statsSetupRequest.getAppId()) {
199                                 AppStats newAppStat = new AppStats(instanceKey, statsSetupRequest);
200                                 stats.remove(st);
201                                 stats.add(newAppStat);
202                                 editStatsObjectFound = true;
203                                 saveStats();
204                                 break;
205                         }
206                 }
207                 if (!editStatsObjectFound) {
208                         String msg = "Stats to be updated does not exist ";
209                         logger.warn(msg);
210                         throw new StatsManagerException(msg);
211                 }
212         }
213
214         public synchronized AppStats deleteStats(String instanceKey, int appId) throws StatsManagerException, IOException {
215                 logger.debug("deleteStats: appId {}, instanceKey {}", appId, instanceKey);
216                 boolean deleteStatsObjectFound = false;
217                 AppStats stat = null;
218                 for (AppStats st : stats) {
219                         if (st.getInstanceKey().equals(instanceKey) && st.getStatsDetails().getAppId() == appId) {
220                                 stat = st;
221                                 stats.remove(stat);
222                                 deleteStatsObjectFound = true;
223                                 try {
224                                         saveStats();
225                                         break;
226                                 } catch (Exception e) {
227                                         throw new StatsManagerException(e.toString());
228                                 }
229
230                         }
231                 }
232                 if (!deleteStatsObjectFound) {
233                         String msg = "deleteStats: no match on app id {} of instance key {}";
234                         logger.warn(msg, appId, instanceKey);
235                         throw new StatsManagerException(msg);
236                 }
237                 return stat;
238         }
239 }