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