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.onap.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.onap.policy.api.PDPNotification;
39 import org.onap.policy.jpa.BackUpMonitorEntity;
40 import org.onap.policy.std.NotificationStore;
41 import org.onap.policy.std.StdPDPNotification;
43 import com.fasterxml.jackson.core.JsonProcessingException;
44 import com.fasterxml.jackson.databind.JsonNode;
45 import com.github.fge.jackson.JsonLoader;
46 import com.github.fge.jsonpatch.JsonPatch;
47 import com.github.fge.jsonpatch.JsonPatchException;
48 import com.github.fge.jsonpatch.diff.JsonDiff;
51 * BackUp Monitor checks Backup Status with the Database and maintains Redundancy for Gateway Applications.
54 public class BackUpMonitor {
55 private static final Logger LOGGER = Logger.getLogger(BackUpMonitor.class.getName());
56 private static final int DEFAULT_PING = 500; // Value is in milliseconds.
57 private static final String PING_INTERVAL = "ping_interval";
58 private static final String MASTER = "MASTER";
59 private static final String SLAVE = "SLAVE";
61 private static BackUpMonitor instance = null;
62 private static String resourceName = null;
63 private static String resourceNodeName = null;
64 private static String notificationRecord = null;
65 private static String lastMasterNotification = null;
66 private static int pingInterval = DEFAULT_PING;
67 private static Boolean masterFlag = false;
68 private static Object lock = new Object();
69 private static Object notificationLock = new Object();
70 private static BackUpHandler handler = null;
71 private static Boolean stopFlag = false;
72 private static Thread t = null;
73 private EntityManager em;
74 private EntityManagerFactory emf;
77 * Enumeration for the Resource Node Naming. Add here if required.
79 public enum ResourceNode {
83 private BackUpMonitor(String resourceNodeName, String resourceName, Properties properties, BackUpHandler handler)
84 throws BackUpMonitorException {
85 init(resourceName, resourceNodeName, handler);
86 // Create Persistence Entity
87 if(!properties.containsKey(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML)){
88 properties.setProperty(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML, "META-INF/persistencePU.xml");
90 emf = Persistence.createEntityManagerFactory("PolicyEngineUtils", properties);
92 LOGGER.error("Unable to create Entity Manger Factory ");
93 throw new BackUpMonitorException("Unable to create Entity Manger Factory");
95 em = emf.createEntityManager();
97 // Check Database if this is Master or Slave.
100 startThread(new BMonitor());
103 private static void startThread(BMonitor bMonitor) {
104 t = new Thread(bMonitor);
108 public static void stop() throws InterruptedException{
117 private static void init(String resourceName, String resourceNodeName, BackUpHandler handler) {
118 BackUpMonitor.resourceNodeName = resourceNodeName;
119 BackUpMonitor.resourceName = resourceName;
120 BackUpMonitor.handler = handler;
125 * Gets the BackUpMonitor Instance if given proper resourceName and properties. Else returns null.
127 * @param resourceNodeName
128 * String format of the Resource Node to which the resource Belongs to.
129 * @param resourceName
130 * String format of the ResourceName. This needs to be Unique.
132 * Properties format of the properties file.
133 * @return BackUpMonitor instance.
135 public static synchronized BackUpMonitor getInstance(String resourceNodeName, String resourceName,
136 Properties properties, BackUpHandler handler) throws BackUpMonitorException {
137 if (resourceNodeName == null || "".equals(resourceNodeName.trim()) || resourceName == null
138 || "".equals(resourceName.trim()) || properties == null || handler == null) {
139 LOGGER.error("Error while getting Instance. Please check resourceName and/or properties file");
141 } else if ((resourceNodeName.equals(ResourceNode.ASTRA.toString())
142 || resourceNodeName.equals(ResourceNode.BRMS.toString())) && validate(properties) && instance == null) {
143 LOGGER.info("Creating Instance of BackUpMonitor");
144 instance = new BackUpMonitor(resourceNodeName, resourceName, properties, handler);
149 // This is to validate given Properties with required values.
150 private static Boolean validate(Properties properties) {
151 if (properties.getProperty("javax.persistence.jdbc.driver") == null
152 || "".equals(properties.getProperty("javax.persistence.jdbc.driver").trim())) {
153 LOGGER.error("javax.persistence.jdbc.driver property is empty");
156 if (properties.getProperty("javax.persistence.jdbc.url") == null
157 || "".equals(properties.getProperty("javax.persistence.jdbc.url").trim())) {
158 LOGGER.error("javax.persistence.jdbc.url property is empty");
161 if (properties.getProperty("javax.persistence.jdbc.user") == null
162 || "".equals(properties.getProperty("javax.persistence.jdbc.user").trim())) {
163 LOGGER.error("javax.persistence.jdbc.user property is empty");
166 if (properties.getProperty(PING_INTERVAL) == null
167 || "".equals(properties.getProperty(PING_INTERVAL).trim())) {
168 LOGGER.info("ping_interval property not specified. Taking default value");
171 pingInterval = Integer.parseInt(properties.getProperty(PING_INTERVAL).trim());
172 } catch (NumberFormatException e) {
173 LOGGER.warn("Ignored invalid proeprty ping_interval. Taking default value: " + pingInterval);
174 pingInterval = DEFAULT_PING;
180 // Sets the Flag for masterFlag to either True or False.
181 private static void setFlag(Boolean flag) {
182 synchronized (lock) {
188 * Gets the Boolean value of Master(True) or Slave mode (False)
190 * @return Boolean flag which if True means that the operation needs to be performed(Master mode) or if false the
191 * operation is in slave mode.
193 public Boolean getFlag() {
194 synchronized (lock) {
199 // BackUpMonitor Thread
200 private class BMonitor implements Runnable {
203 LOGGER.info("Starting BackUpMonitor Thread.. ");
206 TimeUnit.MILLISECONDS.sleep(pingInterval);
208 } catch (Exception e) {
209 LOGGER.error("Error during Thread execution " + e.getMessage(), e);
216 private static BackUpMonitorEntity setMaster(BackUpMonitorEntity bMEntity) {
217 bMEntity.setFlag(MASTER);
223 private static BackUpMonitorEntity setSlave(BackUpMonitorEntity bMEntity) {
224 bMEntity.setFlag(SLAVE);
229 // Check Database and set the Flag.
230 private void checkDataBase() throws BackUpMonitorException {
231 EntityTransaction et = null;
233 et = em.getTransaction();
234 setNotificationRecord();
236 LOGGER.info("Clearing Cache");
237 em.getEntityManagerFactory().getCache().evictAll();
238 LOGGER.info("Checking Datatbase for BackUpMonitor.. ");
240 Query query = em.createQuery("select b from BackUpMonitorEntity b where b.resourceNodeName = :nn");
241 if (resourceNodeName.equals(ResourceNode.ASTRA.toString())) {
242 query.setParameter("nn", ResourceNode.ASTRA.toString());
243 } else if (resourceNodeName.equals(ResourceNode.BRMS.toString())) {
244 query.setParameter("nn", ResourceNode.BRMS.toString());
246 List<?> bMList = query.getResultList();
247 if (bMList.isEmpty()) {
248 // This is New. create an entry as Master.
249 LOGGER.info("Adding resource " + resourceName + " to Database");
250 BackUpMonitorEntity bMEntity = new BackUpMonitorEntity();
251 bMEntity.setResourceNodeName(resourceNodeName);
252 bMEntity.setResourceName(resourceName);
253 bMEntity = setMaster(bMEntity);
254 bMEntity.setTimeStamp(new Date());
255 em.persist(bMEntity);
258 // Check for other Master(s)
259 ArrayList<BackUpMonitorEntity> masterEntities = new ArrayList<>();
261 BackUpMonitorEntity selfEntity = null;
262 // Check backup monitor entities.
263 for (int i = 0; i < bMList.size(); i++) {
264 BackUpMonitorEntity bMEntity = (BackUpMonitorEntity) bMList.get(i);
265 LOGGER.info("Refreshing Entity. ");
266 em.refresh(bMEntity);
267 if (bMEntity.getFlag().equalsIgnoreCase(MASTER)) {
268 masterEntities.add(bMEntity);
270 if (bMEntity.getResourceName().equals(resourceName)) {
271 selfEntity = bMEntity;
274 if (selfEntity != null) {
275 LOGGER.info("Resource Name already Exists: " + resourceName);
276 if (selfEntity.getFlag().equalsIgnoreCase(MASTER)) {
277 // Already Master Mode.
279 LOGGER.info(resourceName + " is on Master Mode");
280 selfEntity.setTimeStamp(new Date());
281 selfEntity.setNotificationRecord(notificationRecord);
282 em.persist(selfEntity);
284 setLastNotification(null);
285 if (!masterEntities.contains(selfEntity)) {
286 masterEntities.add(selfEntity);
289 // Already Slave Mode.
291 selfEntity.setTimeStamp(new Date());
292 selfEntity.setNotificationRecord(notificationRecord);
293 em.persist(selfEntity);
295 LOGGER.info(resourceName + " is on Slave Mode");
298 // Resource name is null -> No resource with same name.
299 selfEntity = new BackUpMonitorEntity();
300 selfEntity.setResourceNodeName(resourceNodeName);
301 selfEntity.setResourceName(resourceName);
302 selfEntity.setTimeStamp(new Date());
303 selfEntity = setSlave(selfEntity);
304 setLastNotification(null);
305 LOGGER.info("Creating: " + resourceName + " on Slave Mode");
306 em.persist(selfEntity);
309 // Correct the database if any errors and perform monitor checks.
310 if (masterEntities.size() != 1 || !getFlag()) {
311 // We are either not master or there are more masters or no masters.
312 if (masterEntities.isEmpty()) {
313 // No Masters is a problem Convert ourselves to Master.
314 selfEntity = setMaster(selfEntity);
315 selfEntity.setTimeStamp(new Date());
316 selfEntity.setNotificationRecord(notificationRecord);
317 LOGGER.info(resourceName + " changed to Master Mode - No Masters available.");
318 em.persist(selfEntity);
321 if (masterEntities.size() > 1) {
322 // More Masters is a problem, Fix the issue by looking for the latest one and make others
324 BackUpMonitorEntity masterEntity = null;
325 for (BackUpMonitorEntity currentEntity : masterEntities) {
326 if (currentEntity.getFlag().equalsIgnoreCase(MASTER)) {
327 if (masterEntity == null) {
328 masterEntity = currentEntity;
329 } else if (currentEntity.getTimeStamp().getTime() > masterEntity.getTimeStamp()
331 // False Master, Update master to slave and take currentMaster as Master.
332 masterEntity.setFlag(SLAVE);
333 masterEntity.setTimeStamp(new Date());
334 em.persist(masterEntity);
336 masterEntity = currentEntity;
338 currentEntity.setFlag(SLAVE);
339 currentEntity.setTimeStamp(new Date());
340 em.persist(currentEntity);
345 masterEntities = new ArrayList<>();
346 masterEntities.add(masterEntity);
348 if (masterEntities.size() == 1) {
349 // Correct Size, Check if Master is Latest, if not Change Master to Slave and Slave to
351 BackUpMonitorEntity masterEntity = masterEntities.get(0);
352 if (!masterEntity.getResourceName().equals(selfEntity.getResourceName())) {
353 Date currentTime = new Date();
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
359 masterEntity.setFlag(SLAVE);
360 String lastNotification = null;
361 if (masterEntity.getNotificationRecord() != null) {
362 lastNotification = calculatePatch(masterEntity.getNotificationRecord());
364 setLastNotification(lastNotification);
365 em.persist(masterEntity);
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);
378 "Backup Monitor Issue, Masters out of sync, This will be fixed in next interval.");
384 } catch (Exception e) {
385 LOGGER.error("failed Database Operation " + e.getMessage(), e);
386 if (et!=null && et.isActive()) {
389 throw new BackUpMonitorException(e);
393 private static void setNotificationRecord() throws BackUpMonitorException {
395 notificationRecord = PolicyUtils.objectToJsonString(NotificationStore.getNotificationRecord());
396 } catch (JsonProcessingException e1) {
397 LOGGER.error("Error retrieving notification record failed. ", e1);
398 throw new BackUpMonitorException(e1);
402 // Calculate Patch and return String JsonPatch of the notification Delta.
403 private synchronized String calculatePatch(String oldNotificationRecord) {
405 JsonNode notification = JsonLoader.fromString(notificationRecord);
406 JsonNode oldNotification = JsonLoader.fromString(oldNotificationRecord);
407 JsonNode patchNode = JsonDiff.asJson(oldNotification, notification);
408 LOGGER.info("Generated JSON Patch is " + patchNode.toString());
409 JsonPatch patch = JsonPatch.fromJson(patchNode);
410 return generatePatchNotification(patch, oldNotification);
411 } catch (IOException e) {
412 LOGGER.error("Error generating Patched " + e.getMessage(), e);
417 private String generatePatchNotification(JsonPatch patch, JsonNode oldNotification) {
419 JsonNode patched = patch.apply(oldNotification);
420 LOGGER.info("Generated New Notification is : " + patched.toString());
421 return patched.toString();
422 } catch (JsonPatchException e) {
423 LOGGER.error("Error generating Patched " + e.getMessage(), e);
429 * Updates Notification in the Database while Performing the health check.
431 * @param notification
432 * String format of notification record to store in the Database.
435 public synchronized void updateNotification() throws BackUpMonitorException {
439 // Take in string notification and send the record delta to Handler.
440 private static void callHandler(String notification) {
441 if (handler != null) {
443 PDPNotification notificationObject = PolicyUtils.jsonStringToObject(notification,
444 StdPDPNotification.class);
445 if (notificationObject.getNotificationType() != null) {
446 LOGGER.info("Performing Patched notification ");
447 performPatchNotification(notificationObject);
450 } catch (IOException e) {
451 LOGGER.info("Error while notification Conversion " + e.getMessage(), e);
456 private static void performPatchNotification(PDPNotification notificationObject) {
458 handler.runOnNotification(notificationObject);
459 notificationRecord = lastMasterNotification;
460 } catch (Exception e) {
461 LOGGER.error("Error in Clients Handler Object : " + e.getMessage(), e);
466 // Used to set LastMasterNotification Record.
467 private static void setLastNotification(String notification) {
468 synchronized (notificationLock) {
469 lastMasterNotification = notification;
470 if (lastMasterNotification != null && !"\"notificationType\":null".equals(lastMasterNotification)) {
471 if (lastMasterNotification.equals(notificationRecord)) {
474 callHandler(notification);