Initial OpenECOMP policy/engine commit
[policy/engine.git] / PolicyEngineUtils / src / main / java / org / openecomp / 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.openecomp.policy.utils;
22
23 import java.io.IOException;
24 import java.util.Date;
25 import java.util.List;
26 import java.util.Properties;
27 import java.util.concurrent.TimeUnit;
28
29 import javax.persistence.EntityManager;
30 import javax.persistence.EntityManagerFactory;
31 import javax.persistence.EntityTransaction;
32 import javax.persistence.Persistence;
33 import javax.persistence.Query;
34
35 import org.apache.log4j.Logger;
36 import org.eclipse.persistence.config.PersistenceUnitProperties;
37 import org.openecomp.policy.api.PDPNotification;
38 import org.openecomp.policy.jpa.BackUpMonitorEntity;
39 import org.openecomp.policy.std.NotificationStore;
40 import org.openecomp.policy.std.StdPDPNotification;
41
42 import com.fasterxml.jackson.databind.JsonNode;
43 import com.github.fge.jackson.JsonLoader;
44 import com.github.fge.jsonpatch.JsonPatch;
45 import com.github.fge.jsonpatch.JsonPatchException;
46 import com.github.fge.jsonpatch.diff.JsonDiff;
47
48 /**
49  * BackUp Monitor checks Backup Status with the Database and maintains Redundancy for Gateway Applications. 
50  * 
51  */
52 public class BackUpMonitor {
53         private static final Logger logger = Logger.getLogger(BackUpMonitor.class.getName());
54         private static final int DEFAULT_PING = 60000; // Value is in milliseconds. 
55         
56         private static BackUpMonitor instance = null;
57         private static String resourceName = null;
58         private static String resourceNodeName = null;
59         private static String notificationRecord = null;
60         private static String lastMasterNotification= null;
61         private static int pingInterval = DEFAULT_PING;
62         private static Boolean masterFlag = false;
63         private static Object lock = new Object();
64         private static Object notificationLock = new Object();
65         private static BackUpHandler handler= null;
66         private EntityManager em;
67         private EntityManagerFactory emf;
68         
69         /*
70          * Enumeration for the Resource Node Naming. Add here if required. 
71          */
72         public enum ResourceNode{
73                 BRMS,
74                 ASTRA
75         }
76         
77         private BackUpMonitor(String resourceNodeName, String resourceName, Properties properties, BackUpHandler handler) throws Exception{
78                 if(instance == null){
79                         instance = this;
80                 }
81                 BackUpMonitor.resourceNodeName = resourceNodeName;
82                 BackUpMonitor.resourceName = resourceName;
83                 BackUpMonitor.handler = handler; 
84                 // Create Persistence Entity
85                 properties.setProperty(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML, "META-INF/persistencePU.xml");
86                 emf = Persistence.createEntityManagerFactory("PolicyEngineUtils", properties);
87                 if(emf==null){
88                         logger.error("Unable to create Entity Manger Factory ");
89                         throw new Exception("Unable to create Entity Manger Factory");
90                 }
91                 em = emf.createEntityManager();
92                 
93                 // Check Database if this is Master or Slave.
94                 checkDataBase();
95                 
96                 // Start thread.
97                 Thread t = new Thread(new BMonitor());
98                 t.start();
99         }
100
101         /**
102          * Gets the BackUpMonitor Instance if given proper resourceName and properties. Else returns null. 
103          * 
104          * @param resourceNodeName String format of the Resource Node to which the resource Belongs to. 
105          * @param resourceName String format of the ResourceName. This needs to be Unique. 
106          * @param properties Properties format of the properties file. 
107          * @return BackUpMonitor instance. 
108          */
109         public static synchronized BackUpMonitor getInstance(String resourceNodeName, String resourceName, Properties properties, BackUpHandler handler) throws Exception {
110                 if(resourceNodeName==null || resourceNodeName.trim().equals("") ||resourceName==null|| resourceName.trim().equals("") || properties == null || handler==null){
111                         logger.error("Error while getting Instance. Please check resourceName and/or properties file");
112                         return null;
113                 }else if((resourceNodeName.equals(ResourceNode.ASTRA.toString()) || resourceNodeName.equals(ResourceNode.BRMS.toString())) && validate(properties) && instance==null){
114                         logger.info("Creating Instance of BackUpMonitor");
115                         instance = new BackUpMonitor(resourceNodeName, resourceName, properties, handler);
116                 }
117                 return instance;
118         }
119         
120         // This is to validate given Properties with required values. 
121         private static Boolean validate(Properties properties){
122                 if(properties.getProperty("javax.persistence.jdbc.driver")==null ||properties.getProperty("javax.persistence.jdbc.driver").trim().equals("")){
123                         logger.error("javax.persistence.jdbc.driver property is empty");
124                         return false;
125                 }
126                 if(properties.getProperty("javax.persistence.jdbc.url")==null || properties.getProperty("javax.persistence.jdbc.url").trim().equals("")){
127                         logger.error("javax.persistence.jdbc.url property is empty");
128                         return false;
129                 }
130                 if(properties.getProperty("javax.persistence.jdbc.user")==null || properties.getProperty("javax.persistence.jdbc.user").trim().equals("")){
131                         logger.error("javax.persistence.jdbc.user property is empty");
132                         return false;
133                 }
134                 if(properties.getProperty("javax.persistence.jdbc.password")==null || properties.getProperty("javax.persistence.jdbc.password").trim().equals("")){
135                         logger.error("javax.persistence.jdbc.password property is empty");
136                         return false;
137                 }
138                 if(properties.getProperty("ping_interval")==null || properties.getProperty("ping_interval").trim().equals("")){
139                         logger.info("ping_interval property not specified. Taking default value");
140                 }else{
141                         try{
142                                 pingInterval = Integer.parseInt(properties.getProperty("ping_interval").trim());
143                         }catch(NumberFormatException e){
144                                 logger.warn("Ignored invalid proeprty ping_interval. Taking default value.");
145                                 pingInterval = DEFAULT_PING;
146                         }
147                 }
148                 return true;
149         }
150         
151         // Sets the Flag for masterFlag to either True or False. 
152         private static void setFlag(Boolean flag){
153                 synchronized (lock) {
154                         masterFlag = flag;
155                 }
156         }
157         
158         /**
159          * Gets the Boolean value of Master(True) or Slave mode (False)
160          * 
161          * @return Boolean flag which if True means that the operation needs to be performed(Master mode) or if false the operation is in slave mode. 
162          */
163         public synchronized Boolean getFlag(){
164                 synchronized (lock) {
165                         return masterFlag;
166                 }
167         }
168         
169         // BackUpMonitor Thread 
170         private class BMonitor implements Runnable{
171                 @Override
172                 public void run() {
173                         logger.info("Starting BackUpMonitor Thread.. ");
174                         while(true){
175                                 try {
176                                         TimeUnit.MILLISECONDS.sleep(pingInterval);
177                                         checkDataBase();
178                                 } catch (Exception e) {
179                                         logger.error("Error during Thread execution " + e.getMessage());
180                                 }
181                         }
182                 }
183         }
184         
185         // Set Master 
186         private static BackUpMonitorEntity setMaster(BackUpMonitorEntity bMEntity){
187                 bMEntity.setFlag("MASTER");
188                 setFlag(true);
189                 return bMEntity;
190         }
191         
192         // Set Slave 
193         private static BackUpMonitorEntity setSlave(BackUpMonitorEntity bMEntity){
194                 bMEntity.setFlag("SLAVE");
195                 setFlag(false);
196                 return bMEntity;
197         }
198         
199         // Check Database and set the Flag. 
200         private void checkDataBase() throws Exception {
201                 EntityTransaction et = em.getTransaction();
202                 notificationRecord = PolicyUtils.objectToJsonString(NotificationStore.getNotificationRecord());
203                 // Clear Cache. 
204                 logger.info("Clearing Cache");
205                 em.getEntityManagerFactory().getCache().evictAll();
206                 try{
207                         logger.info("Checking Datatbase for BackUpMonitor.. ");
208                         et.begin();
209                         Query query = em.createQuery("select b from BackUpMonitorEntity b where b.resourceNodeName = :nn");
210                         if(resourceNodeName.equals(ResourceNode.ASTRA.toString())){
211                                 query.setParameter("nn", ResourceNode.ASTRA.toString());
212                         }else if(resourceNodeName.equals(ResourceNode.BRMS.toString())){
213                                 query.setParameter("nn", ResourceNode.BRMS.toString());
214                         }
215                         List<?> bMList = query.getResultList();
216                         if(bMList.isEmpty()){
217                                 // This is New. create an entry as Master. 
218                                 logger.info("Adding resource " + resourceName + " to Database");
219                                 BackUpMonitorEntity bMEntity = new BackUpMonitorEntity();
220                                 bMEntity.setResoruceNodeName(resourceNodeName);
221                                 bMEntity.setResourceName(resourceName);
222                                 bMEntity = setMaster(bMEntity);
223                                 bMEntity.setTimeStamp(new Date());
224                                 em.persist(bMEntity);
225                                 em.flush();
226                         }else{
227                                 // Check if resourceName already exists and if there is a Master in Node.
228                                 Boolean masterFlag = false;
229                                 Boolean alreadyMaster = false;
230                                 Date currentTime;
231                                 BackUpMonitorEntity masterEntity = null;
232                                 BackUpMonitorEntity slaveEntity = null;
233                                 long timeDiff = 0;
234                                 for(int i=0; i< bMList.size(); i++){
235                                         BackUpMonitorEntity bMEntity = (BackUpMonitorEntity) bMList.get(i);
236                                         logger.info("Refreshing Entity. ");
237                                         em.refresh(bMEntity);
238                                         if(bMEntity.getResourceName().equals(resourceName)){
239                                                 logger.info("Resource Name already Exists. " + resourceName);
240                                                 if(bMEntity.getFlag().equalsIgnoreCase("MASTER")){
241                                                         // Master mode
242                                                         setFlag(true);
243                                                         logger.info(resourceName + " is on Master Mode");
244                                                         bMEntity.setTimeStamp(new Date());
245                                                         bMEntity.setNotificationRecord(notificationRecord);
246                                                         em.persist(bMEntity);
247                                                         em.flush();
248                                                         setLastNotification(null);
249                                                         alreadyMaster = true;
250                                                         break;
251                                                 }else{
252                                                         // Slave mode. 
253                                                         setFlag(false);
254                                                         slaveEntity = bMEntity;
255                                                         logger.info(resourceName + " is on Slave Mode");
256                                                 }
257                                         }else{
258                                                 if(bMEntity.getFlag().equalsIgnoreCase("MASTER")){
259                                                         // check if its time stamp is old.
260                                                         currentTime = new Date();
261                                                         timeDiff = currentTime.getTime()-bMEntity.getTimeStamp().getTime();
262                                                         masterEntity = bMEntity;
263                                                         masterFlag = true;
264                                                 }
265                                         }
266                                 }
267                                 // If there is master and no slave entry then add the slave entry to database. 
268                                 // If we are slave and there is a masterFlag then check timeStamp to find if master is down. 
269                                 if(!alreadyMaster){
270                                         BackUpMonitorEntity bMEntity;
271                                         if(slaveEntity==null){
272                                                 bMEntity = new BackUpMonitorEntity();
273                                         }else{
274                                                 bMEntity = slaveEntity;
275                                         }
276                                         if(masterFlag && !getFlag()){
277                                                 if(timeDiff > pingInterval){
278                                                         // This is down or has an issue and we need to become Master while turning the Master to slave. 
279                                                         masterEntity = setSlave(masterEntity);
280                                                         String lastNotification = null;
281                                                         if(masterEntity.getNotificationRecord()!=null){
282                                                                 lastNotification = calculatePatch(masterEntity.getNotificationRecord());
283                                                         }
284                                                         setLastNotification(lastNotification);
285                                                         em.persist(masterEntity);
286                                                         em.flush();
287                                                         bMEntity = setMaster(bMEntity);
288                                                         logger.info(resourceName + " changed to Master Mode");
289                                                 }else{
290                                                         bMEntity = setSlave(bMEntity);
291                                                         setLastNotification(null);
292                                                         logger.info(resourceName + " is on Slave Mode");
293                                                 }
294                                         }else{
295                                                 // If there is no Master. we need to become Master. 
296                                                 bMEntity = setMaster(bMEntity);
297                                                 logger.info(resourceName + " is on Master Mode");
298                                                 setLastNotification(null);
299                                         }
300                                         bMEntity.setNotificationRecord(notificationRecord);
301                                         bMEntity.setResoruceNodeName(resourceNodeName);
302                                         bMEntity.setResourceName(resourceName);
303                                         bMEntity.setTimeStamp(new Date());
304                                         em.persist(bMEntity);
305                                         em.flush();
306                                 }
307                         }
308                         et.commit();
309                 }catch(Exception e){
310                         logger.error("failed Database Operation " + e.getMessage());
311                         if(et.isActive()){
312                                 et.rollback();
313                         }
314                         throw new Exception(e);
315                 }
316         }
317         
318         // Calculate Patch and return String JsonPatch of the notification Delta. 
319         private synchronized String calculatePatch(String oldNotificationRecord) {
320                 try{
321                         JsonNode notification = JsonLoader.fromString(notificationRecord);
322                         JsonNode oldNotification = JsonLoader.fromString(oldNotificationRecord);
323                         JsonNode patchNode = JsonDiff.asJson(oldNotification, notification);
324                         logger.info("Generated JSON Patch is " + patchNode.toString());
325                         JsonPatch patch = JsonPatch.fromJson(patchNode);
326                         try {
327                                 JsonNode patched = patch.apply(oldNotification);
328                                 logger.info("Generated New Notification is : " + patched.toString());
329                                 return patched.toString();
330                         } catch (JsonPatchException e) {
331                                 logger.error("Error generating Patched "  +e.getMessage());
332                                 return null;
333                         }
334                 }catch(IOException e){
335                         logger.error("Error generating Patched "  +e.getMessage());
336                         return null;
337                 }
338         }
339
340         /**
341          * Updates Notification in the Database while Performing the health check. 
342          * 
343          * @param notification String format of notification record to store in the Database. 
344          * @throws Exception 
345          */
346         public synchronized void updateNotification() throws Exception{
347                 checkDataBase();
348         }
349         
350         // Take in string notification and send the record delta to Handler. 
351         private static void callHandler(String notification){
352                 if(handler!=null){
353                         try {
354                                 PDPNotification notificationObject = PolicyUtils.jsonStringToObject(notification, StdPDPNotification.class);
355                                 if(notificationObject.getNotificationType()!=null){
356                                         logger.info("Performing Patched notification ");
357                                         try{
358                                                 handler.runOnNotification(notificationObject);
359                                         }catch (Exception e){
360                                                 logger.error("Error in Clients Handler Object : " + e.getMessage());
361                                         }
362                                 }
363                         } catch (IOException e) {
364                                 logger.info("Error while notification Conversion " + e.getMessage());
365                         }
366                 }
367         }
368         
369         // Used to set LastMasterNotification Record. 
370         private static void setLastNotification(String notification){
371                 synchronized(notificationLock){
372                         lastMasterNotification = notification;
373                         if(lastMasterNotification!=null && !lastMasterNotification.equals("\"notificationType\":null")){
374                                 callHandler(notification);
375                         }
376                 }
377         }
378 }