2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.openecomp.policy.utils;
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;
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;
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;
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;
50 * BackUp Monitor checks Backup Status with the Database and maintains Redundancy for Gateway Applications.
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.
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;
71 * Enumeration for the Resource Node Naming. Add here if required.
73 public enum ResourceNode{
78 private BackUpMonitor(String resourceNodeName, String resourceName, Properties properties, BackUpHandler handler) throws Exception{
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);
89 LOGGER.error("Unable to create Entity Manger Factory ");
90 throw new Exception("Unable to create Entity Manger Factory");
92 em = emf.createEntityManager();
94 // Check Database if this is Master or Slave.
98 Thread t = new Thread(new BMonitor());
103 * Gets the BackUpMonitor Instance if given proper resourceName and properties. Else returns null.
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.
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");
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);
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");
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");
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");
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");
139 if(properties.getProperty("ping_interval")==null || properties.getProperty("ping_interval").trim().equals("")){
140 LOGGER.info("ping_interval property not specified. Taking default value");
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;
152 // Sets the Flag for masterFlag to either True or False.
153 private static void setFlag(Boolean flag){
154 synchronized (lock) {
160 * Gets the Boolean value of Master(True) or Slave mode (False)
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.
164 public Boolean getFlag(){
165 synchronized (lock) {
170 // BackUpMonitor Thread
171 private class BMonitor implements Runnable{
174 LOGGER.info("Starting BackUpMonitor Thread.. ");
177 TimeUnit.MILLISECONDS.sleep(pingInterval);
179 } catch (Exception e) {
180 LOGGER.error("Error during Thread execution " + e.getMessage());
187 private static BackUpMonitorEntity setMaster(BackUpMonitorEntity bMEntity){
188 bMEntity.setFlag("MASTER");
194 private static BackUpMonitorEntity setSlave(BackUpMonitorEntity bMEntity){
195 bMEntity.setFlag("SLAVE");
200 // Check Database and set the Flag.
201 private void checkDataBase() throws Exception {
202 EntityTransaction et = em.getTransaction();
203 notificationRecord = PolicyUtils.objectToJsonString(NotificationStore.getNotificationRecord());
205 LOGGER.info("Clearing Cache");
206 em.getEntityManagerFactory().getCache().evictAll();
208 LOGGER.info("Checking Datatbase for BackUpMonitor.. ");
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());
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);
228 // Check for other Master(s)
229 ArrayList<BackUpMonitorEntity> masterEntities = new ArrayList<BackUpMonitorEntity>();
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);
240 if(bMEntity.getResourceName().equals(resourceName)){
241 selfEntity = bMEntity;
244 if(selfEntity!=null){
245 LOGGER.info("Resource Name already Exists: " + resourceName);
246 if(selfEntity.getFlag().equalsIgnoreCase("MASTER")){
247 // Already Master Mode.
249 LOGGER.info(resourceName + " is on Master Mode");
250 selfEntity.setTimeStamp(new Date());
251 selfEntity.setNotificationRecord(notificationRecord);
252 em.persist(selfEntity);
254 setLastNotification(null);
255 if(!masterEntities.contains(selfEntity)){
256 masterEntities.add(selfEntity);
259 // Already Slave Mode.
261 selfEntity.setTimeStamp(new Date());
262 selfEntity.setNotificationRecord(notificationRecord);
263 em.persist(selfEntity);
265 LOGGER.info(resourceName + " is on Slave Mode");
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);
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);
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);
304 masterEntity = currentEntity;
306 currentEntity.setFlag("SLAVE");
307 currentEntity.setTimeStamp(new Date());
308 em.persist(currentEntity);
313 masterEntities = new ArrayList<BackUpMonitorEntity>();
314 masterEntities.add(masterEntity);
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();
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());
330 setLastNotification(lastNotification);
331 em.persist(masterEntity);
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);
343 LOGGER.error("Backup Monitor Issue, Masters out of sync, This will be fixed in next interval.");
350 LOGGER.error("failed Database Operation " + e.getMessage());
354 throw new Exception(e);
358 // Calculate Patch and return String JsonPatch of the notification Delta.
359 private synchronized String calculatePatch(String oldNotificationRecord) {
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);
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());
374 }catch(IOException e){
375 LOGGER.error("Error generating Patched " +e.getMessage());
381 * Updates Notification in the Database while Performing the health check.
383 * @param notification String format of notification record to store in the Database.
386 public synchronized void updateNotification() throws Exception{
390 // Take in string notification and send the record delta to Handler.
391 private static void callHandler(String notification){
394 PDPNotification notificationObject = PolicyUtils.jsonStringToObject(notification, StdPDPNotification.class);
395 if(notificationObject.getNotificationType()!=null){
396 LOGGER.info("Performing Patched notification ");
398 handler.runOnNotification(notificationObject);
399 notificationRecord = lastMasterNotification;
400 }catch (Exception e){
401 LOGGER.error("Error in Clients Handler Object : " + e.getMessage());
404 } catch (IOException e) {
405 LOGGER.info("Error while notification Conversion " + e.getMessage());
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)){
418 callHandler(notification);