Redundant code removal and hadrcoded strings
[policy/engine.git] / PolicyEngineUtils / src / main / java / org / onap / policy / utils / BackUpMonitor.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * PolicyEngineUtils
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. 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  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.utils;
22
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Date;
26 import java.util.List;
27 import java.util.Properties;
28 import java.util.concurrent.TimeUnit;
29
30 import javax.persistence.EntityManager;
31 import javax.persistence.EntityManagerFactory;
32 import javax.persistence.EntityTransaction;
33 import javax.persistence.Persistence;
34 import javax.persistence.Query;
35
36 import org.apache.log4j.Logger;
37 import org.eclipse.persistence.config.PersistenceUnitProperties;
38 import org.onap.policy.api.PDPNotification;
39 import org.onap.policy.jpa.BackUpMonitorEntity;
40 import org.onap.policy.std.NotificationStore;
41 import org.onap.policy.std.StdPDPNotification;
42
43 import com.fasterxml.jackson.core.JsonProcessingException;
44 import com.fasterxml.jackson.databind.JsonNode;
45 import com.github.fge.jackson.JsonLoader;
46 import com.github.fge.jsonpatch.JsonPatch;
47 import com.github.fge.jsonpatch.JsonPatchException;
48 import com.github.fge.jsonpatch.diff.JsonDiff;
49
50 /**
51  * BackUp Monitor checks Backup Status with the Database and maintains Redundancy for Gateway Applications.
52  * 
53  */
54 public class BackUpMonitor {
55     private static final Logger LOGGER = Logger.getLogger(BackUpMonitor.class.getName());
56     private static final int DEFAULT_PING = 500; // Value is in milliseconds.
57     private static final String PING_INTERVAL = "ping_interval";
58     private static final String MASTER = "MASTER";
59     private static final String SLAVE = "SLAVE";
60     
61     private static BackUpMonitor instance = null;
62     private static String resourceName = null;
63     private static String resourceNodeName = null;
64     private static String notificationRecord = null;
65     private static String lastMasterNotification = null;
66     private static int pingInterval = DEFAULT_PING;
67     private static Boolean masterFlag = false;
68     private static Object lock = new Object();
69     private static Object notificationLock = new Object();
70     private static BackUpHandler handler = null;
71     private static Boolean stopFlag = false;
72     private static Thread t = null;
73     private EntityManager em;
74     private EntityManagerFactory emf;
75
76     /*
77      * Enumeration for the Resource Node Naming. Add here if required.
78      */
79     public enum ResourceNode {
80         BRMS, ASTRA
81     }
82
83     private BackUpMonitor(String resourceNodeName, String resourceName, Properties properties, BackUpHandler handler)
84             throws BackUpMonitorException {
85         init(resourceName, resourceNodeName, handler);
86         // Create Persistence Entity
87         if(!properties.containsKey(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML)){
88             properties.setProperty(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML, "META-INF/persistencePU.xml");
89         }
90         emf = Persistence.createEntityManagerFactory("PolicyEngineUtils", properties);
91         if (emf == null) {
92             LOGGER.error("Unable to create Entity Manger Factory ");
93             throw new BackUpMonitorException("Unable to create Entity Manger Factory");
94         }
95         em = emf.createEntityManager();
96
97         // Check Database if this is Master or Slave.
98         checkDataBase();
99         // Start thread.
100         startThread(new BMonitor());
101     }
102     
103     private static void startThread(BMonitor bMonitor) {
104         t = new Thread(bMonitor);
105         t.start();
106     }
107
108     public static void stop() throws InterruptedException{
109         stopFlag = true;
110         if(t!=null){
111             t.interrupt();
112             t.join();
113         }
114         instance = null;
115     }
116
117     private static void init(String resourceName, String resourceNodeName, BackUpHandler handler) {
118         BackUpMonitor.resourceNodeName = resourceNodeName;
119         BackUpMonitor.resourceName = resourceName;
120         BackUpMonitor.handler = handler;
121         stopFlag = false;
122     }
123
124     /**
125      * Gets the BackUpMonitor Instance if given proper resourceName and properties. Else returns null.
126      * 
127      * @param resourceNodeName
128      *            String format of the Resource Node to which the resource Belongs to.
129      * @param resourceName
130      *            String format of the ResourceName. This needs to be Unique.
131      * @param properties
132      *            Properties format of the properties file.
133      * @return BackUpMonitor instance.
134      */
135     public static synchronized BackUpMonitor getInstance(String resourceNodeName, String resourceName,
136             Properties properties, BackUpHandler handler) throws BackUpMonitorException {
137         if (resourceNodeName == null || resourceNodeName.trim().equals("") || resourceName == null
138                 || resourceName.trim().equals("") || properties == null || handler == null) {
139             LOGGER.error("Error while getting Instance. Please check resourceName and/or properties file");
140             return null;
141         } else if ((resourceNodeName.equals(ResourceNode.ASTRA.toString())
142                 || resourceNodeName.equals(ResourceNode.BRMS.toString())) && validate(properties) && instance == null) {
143             LOGGER.info("Creating Instance of BackUpMonitor");
144             instance = new BackUpMonitor(resourceNodeName, resourceName, properties, handler);
145         }
146         return instance;
147     }
148
149     // This is to validate given Properties with required values.
150     private static Boolean validate(Properties properties) {
151         if (properties.getProperty("javax.persistence.jdbc.driver") == null
152                 || properties.getProperty("javax.persistence.jdbc.driver").trim().equals("")) {
153             LOGGER.error("javax.persistence.jdbc.driver property is empty");
154             return false;
155         }
156         if (properties.getProperty("javax.persistence.jdbc.url") == null
157                 || properties.getProperty("javax.persistence.jdbc.url").trim().equals("")) {
158             LOGGER.error("javax.persistence.jdbc.url property is empty");
159             return false;
160         }
161         if (properties.getProperty("javax.persistence.jdbc.user") == null
162                 || properties.getProperty("javax.persistence.jdbc.user").trim().equals("")) {
163             LOGGER.error("javax.persistence.jdbc.user property is empty");
164             return false;
165         }
166         if (properties.getProperty(PING_INTERVAL) == null
167                 || properties.getProperty(PING_INTERVAL).trim().equals("")) {
168             LOGGER.info("ping_interval property not specified. Taking default value");
169         } else {
170             try {
171                 pingInterval = Integer.parseInt(properties.getProperty(PING_INTERVAL).trim());
172             } catch (NumberFormatException e) {
173                 LOGGER.warn("Ignored invalid proeprty ping_interval. Taking default value: " + pingInterval);
174                 pingInterval = DEFAULT_PING;
175             }
176         }
177         return true;
178     }
179
180     // Sets the Flag for masterFlag to either True or False.
181     private static void setFlag(Boolean flag) {
182         synchronized (lock) {
183             masterFlag = flag;
184         }
185     }
186
187     /**
188      * Gets the Boolean value of Master(True) or Slave mode (False)
189      * 
190      * @return Boolean flag which if True means that the operation needs to be performed(Master mode) or if false the
191      *         operation is in slave mode.
192      */
193     public Boolean getFlag() {
194         synchronized (lock) {
195             return masterFlag;
196         }
197     }
198
199     // BackUpMonitor Thread
200     private class BMonitor implements Runnable {
201         @Override
202         public void run() {
203             LOGGER.info("Starting BackUpMonitor Thread.. ");
204             while (!stopFlag) {
205                 try {
206                     TimeUnit.MILLISECONDS.sleep(pingInterval);
207                     checkDataBase();
208                 } catch (Exception e) {
209                     LOGGER.error("Error during Thread execution " + e.getMessage(), e);
210                 }
211             }
212         }
213     }
214
215     // Set Master
216     private static BackUpMonitorEntity setMaster(BackUpMonitorEntity bMEntity) {
217         bMEntity.setFlag(MASTER);
218         setFlag(true);
219         return bMEntity;
220     }
221
222     // Set Slave
223     private static BackUpMonitorEntity setSlave(BackUpMonitorEntity bMEntity) {
224         bMEntity.setFlag(SLAVE);
225         setFlag(false);
226         return bMEntity;
227     }
228
229     // Check Database and set the Flag.
230     private void checkDataBase() throws BackUpMonitorException {
231         EntityTransaction et = null;
232         try {
233             et = em.getTransaction();
234             setNotificationRecord();
235             // Clear Cache.
236             LOGGER.info("Clearing Cache");
237             em.getEntityManagerFactory().getCache().evictAll();
238             LOGGER.info("Checking Datatbase for BackUpMonitor.. ");
239             et.begin();
240             Query query = em.createQuery("select b from BackUpMonitorEntity b where b.resourceNodeName = :nn");
241             if (resourceNodeName.equals(ResourceNode.ASTRA.toString())) {
242                 query.setParameter("nn", ResourceNode.ASTRA.toString());
243             } else if (resourceNodeName.equals(ResourceNode.BRMS.toString())) {
244                 query.setParameter("nn", ResourceNode.BRMS.toString());
245             }
246             List<?> bMList = query.getResultList();
247             if (bMList.isEmpty()) {
248                 // This is New. create an entry as Master.
249                 LOGGER.info("Adding resource " + resourceName + " to Database");
250                 BackUpMonitorEntity bMEntity = new BackUpMonitorEntity();
251                 bMEntity.setResourceNodeName(resourceNodeName);
252                 bMEntity.setResourceName(resourceName);
253                 bMEntity = setMaster(bMEntity);
254                 bMEntity.setTimeStamp(new Date());
255                 em.persist(bMEntity);
256                 em.flush();
257             } else {
258                 // Check for other Master(s)
259                 ArrayList<BackUpMonitorEntity> masterEntities = new ArrayList<>();
260                 // Check for self.
261                 BackUpMonitorEntity selfEntity = null;
262                 // Check backup monitor entities.
263                 for (int i = 0; i < bMList.size(); i++) {
264                     BackUpMonitorEntity bMEntity = (BackUpMonitorEntity) bMList.get(i);
265                     LOGGER.info("Refreshing Entity. ");
266                     em.refresh(bMEntity);
267                     if (bMEntity.getFlag().equalsIgnoreCase(MASTER)) {
268                         masterEntities.add(bMEntity);
269                     }
270                     if (bMEntity.getResourceName().equals(resourceName)) {
271                         selfEntity = bMEntity;
272                     }
273                 }
274                 if (selfEntity != null) {
275                     LOGGER.info("Resource Name already Exists: " + resourceName);
276                     if (selfEntity.getFlag().equalsIgnoreCase(MASTER)) {
277                         // Already Master Mode.
278                         setFlag(true);
279                         LOGGER.info(resourceName + " is on Master Mode");
280                         selfEntity.setTimeStamp(new Date());
281                         selfEntity.setNotificationRecord(notificationRecord);
282                         em.persist(selfEntity);
283                         em.flush();
284                         setLastNotification(null);
285                         if (!masterEntities.contains(selfEntity)) {
286                             masterEntities.add(selfEntity);
287                         }
288                     } else {
289                         // Already Slave Mode.
290                         setFlag(false);
291                         selfEntity.setTimeStamp(new Date());
292                         selfEntity.setNotificationRecord(notificationRecord);
293                         em.persist(selfEntity);
294                         em.flush();
295                         LOGGER.info(resourceName + " is on Slave Mode");
296                     }
297                 } else {
298                     // Resource name is null -> No resource with same name.
299                     selfEntity = new BackUpMonitorEntity();
300                     selfEntity.setResourceNodeName(resourceNodeName);
301                     selfEntity.setResourceName(resourceName);
302                     selfEntity.setTimeStamp(new Date());
303                     selfEntity = setSlave(selfEntity);
304                     setLastNotification(null);
305                     LOGGER.info("Creating: " + resourceName + " on Slave Mode");
306                     em.persist(selfEntity);
307                     em.flush();
308                 }
309                 // Correct the database if any errors and perform monitor checks.
310                 if (masterEntities.size() != 1 || !getFlag()) {
311                     // We are either not master or there are more masters or no masters.
312                     if (masterEntities.isEmpty()) {
313                         // No Masters is a problem Convert ourselves to Master.
314                         selfEntity = setMaster(selfEntity);
315                         selfEntity.setTimeStamp(new Date());
316                         selfEntity.setNotificationRecord(notificationRecord);
317                         LOGGER.info(resourceName + " changed to Master Mode - No Masters available.");
318                         em.persist(selfEntity);
319                         em.flush();
320                     } else {
321                         if (masterEntities.size() > 1) {
322                             // More Masters is a problem, Fix the issue by looking for the latest one and make others
323                             // Slave.
324                             BackUpMonitorEntity masterEntity = null;
325                             for (BackUpMonitorEntity currentEntity : masterEntities) {
326                                 if (currentEntity.getFlag().equalsIgnoreCase(MASTER)) {
327                                     if (masterEntity == null) {
328                                         masterEntity = currentEntity;
329                                     } else if (currentEntity.getTimeStamp().getTime() > masterEntity.getTimeStamp()
330                                             .getTime()) {
331                                         // False Master, Update master to slave and take currentMaster as Master.
332                                         masterEntity.setFlag(SLAVE);
333                                         masterEntity.setTimeStamp(new Date());
334                                         em.persist(masterEntity);
335                                         em.flush();
336                                         masterEntity = currentEntity;
337                                     } else {
338                                         currentEntity.setFlag(SLAVE);
339                                         currentEntity.setTimeStamp(new Date());
340                                         em.persist(currentEntity);
341                                         em.flush();
342                                     }
343                                 }
344                             }
345                             masterEntities = new ArrayList<>();
346                             masterEntities.add(masterEntity);
347                         }
348                         if (masterEntities.size() == 1) {
349                             // Correct Size, Check if Master is Latest, if not Change Master to Slave and Slave to
350                             // Master.
351                             BackUpMonitorEntity masterEntity = masterEntities.get(0);
352                             if (!masterEntity.getResourceName().equals(selfEntity.getResourceName())) {
353                                 Date currentTime = new Date();
354                                 long timeDiff = 0;
355                                 timeDiff = currentTime.getTime() - masterEntity.getTimeStamp().getTime();
356                                 if (timeDiff > (pingInterval + 1500)) {
357                                     // This is down or has an issue and we need to become Master while turning the
358                                     // Master to slave.
359                                     masterEntity.setFlag(SLAVE);
360                                     String lastNotification = null;
361                                     if (masterEntity.getNotificationRecord() != null) {
362                                         lastNotification = calculatePatch(masterEntity.getNotificationRecord());
363                                     }
364                                     setLastNotification(lastNotification);
365                                     em.persist(masterEntity);
366                                     em.flush();
367                                     // Lets Become Master.
368                                     selfEntity = setMaster(selfEntity);
369                                     LOGGER.info("Changing " + resourceName + " from slave to Master Mode");
370                                     selfEntity.setTimeStamp(new Date());
371                                     selfEntity.setNotificationRecord(notificationRecord);
372                                     em.persist(selfEntity);
373                                     em.flush();
374                                 }
375                             }
376                         } else {
377                             LOGGER.error(
378                                     "Backup Monitor Issue, Masters out of sync, This will be fixed in next interval.");
379                         }
380                     }
381                 }
382             }
383             et.commit();
384         } catch (Exception e) {
385             LOGGER.error("failed Database Operation " + e.getMessage(), e);
386             if (et!=null && et.isActive()) {
387                 et.rollback();
388             }
389             throw new BackUpMonitorException(e);
390         }
391     }
392
393     private static void setNotificationRecord() throws BackUpMonitorException {
394         try {
395             notificationRecord = PolicyUtils.objectToJsonString(NotificationStore.getNotificationRecord());
396         } catch (JsonProcessingException e1) {
397             LOGGER.error("Error retrieving notification record failed. ", e1);
398             throw new BackUpMonitorException(e1);
399         }
400     }
401
402     // Calculate Patch and return String JsonPatch of the notification Delta.
403     private synchronized String calculatePatch(String oldNotificationRecord) {
404         try {
405             JsonNode notification = JsonLoader.fromString(notificationRecord);
406             JsonNode oldNotification = JsonLoader.fromString(oldNotificationRecord);
407             JsonNode patchNode = JsonDiff.asJson(oldNotification, notification);
408             LOGGER.info("Generated JSON Patch is " + patchNode.toString());
409             JsonPatch patch = JsonPatch.fromJson(patchNode);
410             try {
411                 JsonNode patched = patch.apply(oldNotification);
412                 LOGGER.info("Generated New Notification is : " + patched.toString());
413                 return patched.toString();
414             } catch (JsonPatchException e) {
415                 LOGGER.error("Error generating Patched " + e.getMessage(), e);
416                 return null;
417             }
418         } catch (IOException e) {
419             LOGGER.error("Error generating Patched " + e.getMessage(), e);
420             return null;
421         }
422     }
423
424     /**
425      * Updates Notification in the Database while Performing the health check.
426      * 
427      * @param notification
428      *            String format of notification record to store in the Database.
429      * @throws Exception
430      */
431     public synchronized void updateNotification() throws BackUpMonitorException {
432         checkDataBase();
433     }
434
435     // Take in string notification and send the record delta to Handler.
436     private static void callHandler(String notification) {
437         if (handler != null) {
438             try {
439                 PDPNotification notificationObject = PolicyUtils.jsonStringToObject(notification,
440                         StdPDPNotification.class);
441                 if (notificationObject.getNotificationType() != null) {
442                     LOGGER.info("Performing Patched notification ");
443                     try {
444                         handler.runOnNotification(notificationObject);
445                         notificationRecord = lastMasterNotification;
446                     } catch (Exception e) {
447                         LOGGER.error("Error in Clients Handler Object : " + e.getMessage(), e);
448                     }
449                 }
450             } catch (IOException e) {
451                 LOGGER.info("Error while notification Conversion " + e.getMessage(), e);
452             }
453         }
454     }
455
456     // Used to set LastMasterNotification Record.
457     private static void setLastNotification(String notification) {
458         synchronized (notificationLock) {
459             lastMasterNotification = notification;
460             if (lastMasterNotification != null && !lastMasterNotification.equals("\"notificationType\":null")) {
461                 if (lastMasterNotification.equals(notificationRecord)) {
462                     return;
463                 }
464                 callHandler(notification);
465             }
466         }
467     }
468 }