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