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