15ed9ad4bee6e4326fd63be29a4fc1798343af59
[appc.git] / appc-dispatcher / appc-dispatcher-common / transaction-recorder / src / main / java / org / onap / appc / transactionrecorder / impl / TransactionRecorderImpl.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : APPC
4  * ================================================================================
5  * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Copyright (C) 2017 Amdocs
8  * ================================================================================
9  * Modifications Copyright (C) 2019 Ericsson
10  * =============================================================================
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  *      http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  *
23  * ============LICENSE_END=========================================================
24  */
25
26 package org.onap.appc.transactionrecorder.impl;
27
28 import org.apache.commons.lang.StringUtils;
29 import org.onap.appc.domainmodel.lcm.Flags;
30 import org.onap.appc.domainmodel.lcm.RequestStatus;
31 import org.onap.appc.domainmodel.lcm.VNFOperation;
32 import org.onap.appc.exceptions.APPCException;
33 import org.onap.appc.transactionrecorder.TransactionRecorder;
34 import org.onap.appc.domainmodel.lcm.TransactionRecord;
35 import org.onap.appc.transactionrecorder.objects.TransactionConstants;
36 import org.onap.ccsdk.sli.core.dblib.DbLibService;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import javax.sql.rowset.CachedRowSet;
41 import java.sql.SQLException;
42 import java.text.ParseException;
43 import java.text.SimpleDateFormat;
44 import java.time.Instant;
45 import java.time.ZoneOffset;
46 import java.time.format.DateTimeFormatter;
47 import java.util.ArrayList;
48 import java.util.List;
49 import java.util.Map;
50 import java.time.temporal.ChronoUnit;
51
52 import static org.onap.appc.transactionrecorder.objects.TransactionConstants.TRANSACTION_ATTRIBUTES.*;
53 import static org.onap.appc.transactionrecorder.objects.TransactionConstants.*;
54
55
56 public class TransactionRecorderImpl implements TransactionRecorder {
57
58     private final String SCHEMA = "sdnctl";
59
60     private String appcInstanceId;
61
62     private DbLibService dbLibService;
63
64     public void setDbLibService(DbLibService dbLibService) {
65         this.dbLibService = dbLibService;
66     }
67
68     private static final Logger logger = LoggerFactory.getLogger(TransactionRecorderImpl.class);
69
70     /**
71      * Stores transaction record to appc database by calling APPC Dao layer.
72      *
73      * @param record Transaction record data.
74      */
75     @Override
76     public void store(TransactionRecord record) throws APPCException {
77         if (logger.isTraceEnabled()) {
78             logger.trace("Transaction data insertion into DB");
79         }
80         final String STORE_DATE_QUERY = TransactionConstants.INSERT_INTO + TransactionConstants.TRANSACTIONS +
81             "(" + TRANSACTION_ID.getColumnName() + TransactionConstants.COMMA +
82             ORIGIN_TIMESTAMP.getColumnName() + TransactionConstants.COMMA +
83             REQUEST_ID.getColumnName() + TransactionConstants.COMMA +
84             SUBREQUEST_ID.getColumnName() + TransactionConstants.COMMA +
85             ORIGINATOR_ID.getColumnName() + TransactionConstants.COMMA +
86             START_TIME.getColumnName() + TransactionConstants.COMMA +
87             END_TIME.getColumnName() + TransactionConstants.COMMA +
88             TARGET_ID.getColumnName() + TransactionConstants.COMMA +
89             TARGET_TYPE.getColumnName() + TransactionConstants.COMMA +
90             OPERATION.getColumnName() + TransactionConstants.COMMA +
91             RESULT_CODE.getColumnName() + TransactionConstants.COMMA +
92             DESCRIPTION.getColumnName() + TransactionConstants.COMMA +
93             STATE.getColumnName() + TransactionConstants.COMMA +
94             SERVICE_INSTANCE_ID + TransactionConstants.COMMA +
95             VNFC_NAME + TransactionConstants.COMMA +
96             VSERVER_ID + TransactionConstants.COMMA +
97             VF_MODULE_ID + TransactionConstants.COMMA +
98             MODE + ") " +
99             "values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
100         try {
101             dbLibService.writeData(STORE_DATE_QUERY, prepareArguments(record), SCHEMA);
102         } catch (SQLException e) {
103             logger.error("Error on storing record " + record.toString(), e);
104             throw new APPCException(ERROR_ACCESSING_DATABASE, e);
105         }
106         if (logger.isTraceEnabled()) {
107             logger.trace("Transaction Data Inserted Successfully into DB");
108         }
109     }
110
111     @Override
112     public void update(String key,
113                        String requestId,
114                        Map<TransactionConstants.TRANSACTION_ATTRIBUTES, String> updateColumns) throws APPCException {
115         logger.debug("Inside update in TransactionRecorderImpl");
116
117         if (appcInstanceId != null && checkIfNullInstanceEntryExist(key, requestId)) {
118             updateNullInstanceEntry(key, requestId, updateColumns);
119         } else {
120             updateTransactionEntry(key, updateColumns);
121         }
122         logger.trace("End of Update Transaction table.");
123     }
124
125     private void updateTransactionEntry(String key, Map<TRANSACTION_ATTRIBUTES, String> updateColumns)
126         throws APPCException {
127         ArrayList<String> values = new ArrayList<>();
128         logger.debug("Inside updateTransactionEntry");
129
130         StringBuilder queryBuilder = new StringBuilder("UPDATE TRANSACTIONS SET ");
131         for (Map.Entry<TransactionConstants.TRANSACTION_ATTRIBUTES, String> entry : updateColumns.entrySet()) {
132             queryBuilder.append(entry.getKey().getColumnName() + " = ? ,");
133             values.add(entry.getValue());
134             logger.debug("Value for " + entry.getKey().getColumnName() + " in Transaction table: " + entry.getValue());
135         }
136         queryBuilder.deleteCharAt(queryBuilder.lastIndexOf(","));
137         queryBuilder.append(WHERE + TRANSACTION_ID.getColumnName() + " = ?");
138         values.add(appcInstanceId + "~" + key);
139
140         String query = queryBuilder.toString();
141         try {
142             if (logger.isDebugEnabled()) {
143                 logger.debug("Before updating Transaction table the Query is: " + query);
144                 for (String value : values) {
145                     logger.debug("Value for Transaction table: " + value);
146                 }
147             }
148             Boolean result = dbLibService.writeData(query, values, SCHEMA);
149             logger.debug("Transactions table data update, writeData() result: " + result);
150         } catch (SQLException e) {
151             logger.error("Error in updating records in updateTransactionEntry " + e);
152             throw new APPCException(ERROR_ACCESSING_DATABASE, e);
153         }
154         if (logger.isTraceEnabled()) {
155             logger.trace("Transaction data updated successfully");
156         }
157
158     }
159
160     private void updateNullInstanceEntry(String key,
161                                          String requestId,
162                                          Map<TRANSACTION_ATTRIBUTES, String> updateColumns) throws APPCException {
163         logger.debug("Inside updateNullInstanceEntry");
164
165         ArrayList<String> values = new ArrayList<>();
166         StringBuilder queryBuilder = new StringBuilder("UPDATE TRANSACTIONS SET ");
167         for (Map.Entry<TransactionConstants.TRANSACTION_ATTRIBUTES, String> entry : updateColumns.entrySet()) {
168             queryBuilder.append(entry.getKey().getColumnName() + " = ? ,");
169             values.add(entry.getValue());
170         }
171         // queryBuilder.deleteCharAt(queryBuilder.lastIndexOf(","));
172         queryBuilder.append(TRANSACTION_ID.getColumnName() + " = ?");
173         queryBuilder.append(WHERE + TRANSACTION_ID.getColumnName() + " = ? AND "
174                             + REQUEST_ID.getColumnName() + " = ? ");
175         values.add(appcInstanceId + "~" + key);
176         values.add(null + "~" + key);
177         values.add(requestId);
178         String query = queryBuilder.toString();
179         try {
180             if (logger.isDebugEnabled()) {
181                 logger.debug("Before updating Transaction table the Query is: " + query);
182                 for (String value : values) {
183                         logger.debug("Value for Transaction table: " + value);
184                 }
185             }
186             Boolean result = dbLibService.writeData(query, values, SCHEMA);
187             logger.debug("Transactions table data update, writeData() result: " + result);
188
189         } catch (SQLException e) {
190             logger.error("Error in updating records in updateNullInstanceEntry " + e);
191             throw new APPCException(ERROR_ACCESSING_DATABASE, e);
192         }
193
194     }
195
196     private boolean checkIfNullInstanceEntryExist(String key, String requestId) throws APPCException {
197         logger.debug("Entered checkIfNullInstanceEntryExist");
198         String nullInstanceCheckQuery = new String("SELECT COUNT(*) as ROWCOUNT  FROM " +
199             TransactionConstants.TRANSACTIONS + WHERE +
200             TRANSACTION_ID.getColumnName() + " = ? AND " +
201             REQUEST_ID.getColumnName() + " = ? ");
202
203         ArrayList<String> nullInstanceCheckParams = new ArrayList<>();
204         nullInstanceCheckParams.add(null + "~" + key);
205         nullInstanceCheckParams.add(requestId);
206
207         try{
208             CachedRowSet rowSet = dbLibService.getData(nullInstanceCheckQuery, nullInstanceCheckParams, SCHEMA);
209             int noRows = 0;
210             if (rowSet != null && rowSet.first()) {
211                 noRows = rowSet.getInt("ROWCOUNT");
212                 logger.info("No of Rows in Transactions Table with TRANSACTION_ID: " +
213                             null + "~" + key + " and REQUEST_ID " + requestId + " is: " + noRows);
214             }
215             if(noRows > 0)
216                 return true;
217         } catch (SQLException e) {
218             logger.error("Error in checkIfNullInstanceEntryExist in the transaction table", e);
219             throw new APPCException(ERROR_ACCESSING_DATABASE, e);
220         }
221         return false;
222     }
223
224     @Override
225     public void markTransactionsAborted(String appcInstanceId) {
226         if (logger.isTraceEnabled()) {
227             logger.trace("marking in progress transactions to aborted");
228         }
229         final String updateQuery =
230             "UPDATE " + TransactionConstants.TRANSACTIONS +
231                 " SET " + STATE.getColumnName() + " = '" + RequestStatus.ABORTED.name() + "',"
232                         + END_TIME.getColumnName() + " = ? " +
233                 WHERE + TRANSACTION_ID.getColumnName() + " LIKE '" + appcInstanceId + "%'  AND "
234                 + STATE.getColumnName() + " in (?,?)";
235
236         if (logger.isDebugEnabled()) {
237             logger.debug("Update query " + updateQuery + " appc-instance-id " + appcInstanceId);
238         }
239
240         ArrayList<String> arguments = new ArrayList<>();
241         arguments.add(dateToStringConverterMillis(Instant.now()));
242         arguments.add(RequestStatus.ACCEPTED.name());
243         arguments.add(RequestStatus.RECEIVED.name());
244         try {
245             dbLibService.writeData(updateQuery, arguments, SCHEMA);
246         } catch (SQLException e) {
247             String message = "In progress transactions couldn't be marked aborted on server start up";
248             logger.error(message);
249             throw new RuntimeException(message, e);
250         }
251         if (logger.isTraceEnabled()) {
252             logger.trace("In progress transactions marked aborted");
253         }
254     }
255
256     @Override
257     public List<TransactionRecord> getInProgressRequests(TransactionRecord record, int interval) throws APPCException {
258
259         String IN_PROGRESS_REQUESTS_QUERY = "SELECT * FROM " +
260             TransactionConstants.TRANSACTIONS + WHERE +
261             TARGET_ID + " = ? AND " +
262             STATE.getColumnName() + " IN (?,?) AND " +
263             START_TIME.getColumnName() + " < ?";
264
265         ArrayList<String> inProgressQueryParams = new ArrayList<>();
266         Instant window = record.getStartTime().minus(interval, ChronoUnit.HOURS);
267         inProgressQueryParams.add(record.getTargetId());
268         inProgressQueryParams.add(RequestStatus.RECEIVED.name());
269         inProgressQueryParams.add(RequestStatus.ACCEPTED.name());
270         inProgressQueryParams.add(dateToStringConverterMillis(record.getStartTime()));
271         if (interval > 0) {
272             IN_PROGRESS_REQUESTS_QUERY += " AND " + START_TIME.getColumnName() + " > ? ";
273             inProgressQueryParams.add(dateToStringConverterMillis(window));
274         }
275
276         try (CachedRowSet rowSet = dbLibService.getData(IN_PROGRESS_REQUESTS_QUERY, inProgressQueryParams, SCHEMA)) {
277             List<TransactionRecord> inProgressRecords = new ArrayList<>();
278             TransactionRecord transaction;
279             while (rowSet.next()) {
280                 transaction = new TransactionRecord();
281                 transaction.setTransactionId(rowSet.getString(TRANSACTION_ID.getColumnName()));
282                 transaction.setRequestId(rowSet.getString(REQUEST_ID.getColumnName()));
283                 transaction.setSubRequestId(rowSet.getString(SUBREQUEST_ID.getColumnName()));
284                 transaction.setOriginatorId(rowSet.getString(ORIGINATOR_ID.getColumnName()));
285                 transaction.setStartTime(stringToDateConverterMillis(rowSet.getString(START_TIME.getColumnName())));
286                 transaction.setTargetId(rowSet.getString(TARGET_ID.getColumnName()));
287                 transaction.setTargetType(rowSet.getString(TARGET_TYPE.getColumnName()));
288                 transaction.setOperation(VNFOperation.valueOf(rowSet.getString(OPERATION.getColumnName())));
289                 transaction.setRequestState(RequestStatus.valueOf(rowSet.getString(STATE.getColumnName())));
290                 transaction.setVnfcName(rowSet.getString(VNFC_NAME.getColumnName()));
291                 transaction.setVserverId(rowSet.getString(VSERVER_ID.getColumnName()));
292                 transaction.setVfModuleId(rowSet.getString(VF_MODULE_ID.getColumnName()));
293                 transaction.setServiceInstanceId(rowSet.getString(SERVICE_INSTANCE_ID.getColumnName()));
294                 transaction.setMode(Flags.Mode.valueOf(rowSet.getString(MODE.getColumnName())));
295                 inProgressRecords.add(transaction);
296             }
297             if (logger.isTraceEnabled()) {
298                 logger.trace("In progress transaction records fetched from database successfully.");
299             }
300             return inProgressRecords;
301         } catch (ParseException e) {
302             logger.error("Error parsing start date during fetching in progress records ", e);
303             throw new APPCException(ERROR_ACCESSING_DATABASE, e);
304         } catch (SQLException e) {
305             logger.error("Error fetching in progress records for Transaction ID = " + appcInstanceId + "~" + record
306                 .getTransactionId(), e);
307             throw new APPCException(ERROR_ACCESSING_DATABASE, e);
308         }
309     }
310
311     @Override
312     public Boolean isTransactionDuplicate(TransactionRecord record) throws APPCException {
313
314         StringBuilder duplicateRequestCheckQuery = new StringBuilder("SELECT " +
315             TRANSACTION_ID.getColumnName() + " FROM " +
316             TransactionConstants.TRANSACTIONS + WHERE +
317             TRANSACTION_ID.getColumnName() + " <> ? AND " +
318             REQUEST_ID.getColumnName() + " = ? AND " +
319             STATE.getColumnName() + " IN(?,?) ");
320
321         ArrayList<String> duplicateCheckParams = new ArrayList<>();
322         duplicateCheckParams.add(appcInstanceId + "~" + record.getTransactionId());
323         duplicateCheckParams.add(record.getRequestId());
324         duplicateCheckParams.add(RequestStatus.RECEIVED.name());
325         duplicateCheckParams.add(RequestStatus.ACCEPTED.name());
326
327         if (!StringUtils.isBlank(record.getSubRequestId())) {
328             duplicateRequestCheckQuery.append(AND + SUBREQUEST_ID.getColumnName() + " = ? ");
329             duplicateCheckParams.add(record.getSubRequestId());
330         } else {
331             duplicateRequestCheckQuery.append(AND + SUBREQUEST_ID.getColumnName() + IS_NULL);
332         }
333         if (!StringUtils.isBlank(record.getOriginatorId())) {
334             duplicateRequestCheckQuery.append(AND + ORIGINATOR_ID.getColumnName() + " = ? ");
335             duplicateCheckParams.add(record.getOriginatorId());
336         } else {
337             duplicateRequestCheckQuery.append(AND + ORIGINATOR_ID.getColumnName() + IS_NULL);
338         }
339         if (logger.isDebugEnabled()) {
340             logger.debug(duplicateRequestCheckQuery.toString());
341         }
342         try (CachedRowSet rowSet = dbLibService.getData(duplicateRequestCheckQuery.toString(), duplicateCheckParams,
343             SCHEMA)) {
344             if (rowSet.first()) {
345                 String transactionId = rowSet.getString(TRANSACTION_ID.getColumnName());
346                 if (logger.isErrorEnabled()) {
347                     logger.error("Duplicate request found. Transaction ID " + transactionId + " is currently in " +
348                         "progress.");
349                 }
350                 return true;
351             }
352             return false;
353         } catch (SQLException e) {
354             logger.error("Error checking duplicate records for Transaction ID = " + appcInstanceId + "~" + record
355                 .getTransactionId(), e);
356             throw new APPCException(ERROR_ACCESSING_DATABASE, e);
357         }
358     }
359
360     @Override
361     public Integer getInProgressRequestsCount() throws APPCException {
362         final String inProgressRequestCountQuery = "SELECT COUNT(*) as VALUE FROM "
363             + TransactionConstants.TRANSACTIONS
364             + WHERE + STATE.getColumnName() + " IN (?,?) ";
365
366         ArrayList<String> checkInProgressParams = new ArrayList<>();
367         checkInProgressParams.add(RequestStatus.RECEIVED.name());
368         checkInProgressParams.add(RequestStatus.ACCEPTED.name());
369         try(CachedRowSet rowSet=dbLibService.getData(inProgressRequestCountQuery,checkInProgressParams,SCHEMA)){
370             if (rowSet.first()) {
371                 int count = rowSet.getInt("VALUE");
372                 logger.info("In progress request count fetched from database successfully.");
373                 return count;
374             }
375         }
376         catch (SQLException e) {
377             logger.error("Error checking in progress request count in the transaction table", e);
378             throw new APPCException(ERROR_ACCESSING_DATABASE, e);
379         }
380         logger.error("Error checking in progress request count in the transaction table");
381         throw new APPCException(ERROR_ACCESSING_DATABASE);
382     }
383
384     @Override
385     public void setAppcInstanceId(String appcInstanceId) {
386         this.appcInstanceId = appcInstanceId;
387     }
388
389
390     @Override
391     public List<RequestStatus> getRecords(String requestId, String subrequestId, String originatorId, String vnfId)
392         throws APPCException {
393         StringBuilder queryString = (new StringBuilder(1024))
394             .append("SELECT " + TRANSACTION_ATTRIBUTES.STATE.getColumnName())
395             .append(" FROM " + TRANSACTIONS)
396             .append(" WHERE " + TRANSACTION_ATTRIBUTES.REQUEST_ID.getColumnName() + "  = ? AND " +
397                 TRANSACTION_ATTRIBUTES.TARGET_ID.getColumnName() + " = ?");
398
399         ArrayList<String> argList = new ArrayList<>();
400         argList.add(requestId);
401         argList.add(vnfId);
402
403         if (subrequestId != null) {
404             queryString.append(" AND " + TRANSACTION_ATTRIBUTES.SUBREQUEST_ID.getColumnName() + " = ?");
405             argList.add(subrequestId);
406         }
407         if (originatorId != null) {
408             queryString.append(" AND " + TRANSACTION_ATTRIBUTES.ORIGINATOR_ID.getColumnName() + " = ?");
409             argList.add(originatorId);
410         }
411
412         List<RequestStatus> requestStatusList = new ArrayList<>();
413         try {
414             CachedRowSet resultSet = dbLibService.getData(queryString.toString(), argList, SCHEMA);
415             while (resultSet.next()) {
416                 String name = resultSet.getString(TRANSACTION_ATTRIBUTES.STATE.getColumnName());
417                 RequestStatus requestStatus = null;
418                 try {
419                     requestStatus = RequestStatus.valueOf(name);
420                 } catch (IllegalArgumentException e) {
421                     logger.error(String.format("Invalid request status (%s) using (%s) :", name, RequestStatus
422                         .UNKNOWN), e);
423                     requestStatus = RequestStatus.UNKNOWN;
424                 }
425                 requestStatusList.add(requestStatus);
426                 logger.debug(String.format("Request Status obtained (%s).", requestStatus));
427             }
428         } catch (SQLException e) {
429             logger.error("Error Accessing Database ", e);
430             throw new APPCException(String.format("Error retrieving record for requestID %s and vnfId %s " +
431                 "from the transactions table", requestId, vnfId), e);
432         }
433
434         return requestStatusList;
435     }
436
437     private ArrayList<String> prepareArguments(TransactionRecord input) {
438         ArrayList<String> arguments = new ArrayList<>();
439         arguments.add(appcInstanceId + "~" + input.getTransactionId());
440         arguments.add(dateToStringConverterMillis(input.getOriginTimestamp()));
441         arguments.add(input.getRequestId());
442         arguments.add(input.getSubRequestId());
443         arguments.add(input.getOriginatorId());
444         arguments.add(dateToStringConverterMillis(input.getStartTime()));
445         arguments.add(dateToStringConverterMillis(input.getEndTime()));
446         arguments.add(input.getTargetId());
447         arguments.add(input.getTargetType());
448         arguments.add(input.getOperation().name());
449         arguments.add(String.valueOf(input.getResultCode()));
450         arguments.add(input.getDescription());
451         arguments.add(input.getRequestState());
452         arguments.add(input.getServiceInstanceId());
453         arguments.add(input.getVnfcName());
454         arguments.add(input.getVserverId());
455         arguments.add(input.getVfModuleId());
456         arguments.add(input.getMode());
457
458         return arguments;
459     }
460
461     private static String dateToStringConverterMillis(Instant date) {
462         if (date == null) {
463             return null;
464         }
465         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(ZoneOffset.UTC);
466         return formatter.format(date);
467     }
468
469     private static Instant stringToDateConverterMillis(String dateString) throws ParseException {
470         SimpleDateFormat customDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
471         return customDate.parse(dateString).toInstant();
472     }
473 }