2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
22 package org.onap.policy.utils;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Date;
27 import java.util.List;
28 import java.util.Properties;
29 import java.util.concurrent.TimeUnit;
31 import javax.persistence.EntityManager;
32 import javax.persistence.EntityManagerFactory;
33 import javax.persistence.EntityTransaction;
34 import javax.persistence.Persistence;
35 import javax.persistence.Query;
37 import org.apache.log4j.Logger;
38 import org.eclipse.persistence.config.PersistenceUnitProperties;
39 import org.onap.policy.api.PDPNotification;
40 import org.onap.policy.jpa.BackUpMonitorEntity;
41 import org.onap.policy.std.NotificationStore;
42 import org.onap.policy.std.StdPDPNotification;
44 import com.fasterxml.jackson.core.JsonProcessingException;
45 import com.fasterxml.jackson.databind.JsonNode;
46 import com.github.fge.jackson.JsonLoader;
47 import com.github.fge.jsonpatch.JsonPatch;
48 import com.github.fge.jsonpatch.JsonPatchException;
49 import com.github.fge.jsonpatch.diff.JsonDiff;
52 * BackUp Monitor checks Backup Status with the Database and maintains Redundancy for Gateway Applications.
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";
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;
78 * Enumeration for the Resource Node Naming. Add here if required.
80 public enum ResourceNode {
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");
91 emf = Persistence.createEntityManagerFactory("PolicyEngineUtils", properties);
93 LOGGER.error("Unable to create Entity Manger Factory ");
94 throw new BackUpMonitorException("Unable to create Entity Manger Factory");
96 em = emf.createEntityManager();
98 // Check Database if this is Master or Slave.
101 startThread(new BMonitor());
104 private static void startThread(BMonitor bMonitor) {
105 t = new Thread(bMonitor);
109 public static void stop() throws InterruptedException{
118 private static void init(String resourceName, String resourceNodeName, BackUpHandler handler) {
119 BackUpMonitor.resourceNodeName = resourceNodeName;
120 BackUpMonitor.resourceName = resourceName;
121 BackUpMonitor.handler = handler;
126 * Gets the BackUpMonitor Instance if given proper resourceName and properties. Else returns null.
128 * @param resourceNodeName
129 * String format of the Resource Node to which the resource Belongs to.
130 * @param resourceName
131 * String format of the ResourceName. This needs to be Unique.
133 * Properties format of the properties file.
134 * @return BackUpMonitor instance.
136 public static synchronized BackUpMonitor getInstance(String resourceNodeName, String resourceName,
137 Properties properties, BackUpHandler handler) throws BackUpMonitorException {
138 if (resourceNodeName == null || "".equals(resourceNodeName.trim()) || resourceName == null
139 || "".equals(resourceName.trim()) || properties == null || handler == null) {
140 LOGGER.error("Error while getting Instance. Please check resourceName and/or properties file");
142 } else if ((resourceNodeName.equals(ResourceNode.ASTRA.toString())
143 || resourceNodeName.equals(ResourceNode.BRMS.toString())) && validate(properties) && instance == null) {
144 LOGGER.info("Creating Instance of BackUpMonitor");
145 instance = new BackUpMonitor(resourceNodeName, resourceName, properties, handler);
150 // This is to validate given Properties with required values.
151 private static Boolean validate(Properties properties) {
152 if (properties.getProperty("javax.persistence.jdbc.driver") == null
153 || "".equals(properties.getProperty("javax.persistence.jdbc.driver").trim())) {
154 LOGGER.error("javax.persistence.jdbc.driver property is empty");
157 if (properties.getProperty("javax.persistence.jdbc.url") == null
158 || "".equals(properties.getProperty("javax.persistence.jdbc.url").trim())) {
159 LOGGER.error("javax.persistence.jdbc.url property is empty");
162 if (properties.getProperty("javax.persistence.jdbc.user") == null
163 || "".equals(properties.getProperty("javax.persistence.jdbc.user").trim())) {
164 LOGGER.error("javax.persistence.jdbc.user property is empty");
167 if (properties.getProperty(PING_INTERVAL) == null
168 || "".equals(properties.getProperty(PING_INTERVAL).trim())) {
169 LOGGER.info("ping_interval property not specified. Taking default value");
172 pingInterval = Integer.parseInt(properties.getProperty(PING_INTERVAL).trim());
173 } catch (NumberFormatException e) {
174 LOGGER.warn("Ignored invalid proeprty ping_interval. Taking default value: " + pingInterval);
175 pingInterval = DEFAULT_PING;
181 // Sets the Flag for masterFlag to either True or False.
182 private static void setFlag(Boolean flag) {
183 synchronized (lock) {
189 * Gets the Boolean value of Master(True) or Slave mode (False)
191 * @return Boolean flag which if True means that the operation needs to be performed(Master mode) or if false the
192 * operation is in slave mode.
194 public Boolean getFlag() {
195 synchronized (lock) {
200 // BackUpMonitor Thread
201 private class BMonitor implements Runnable {
204 LOGGER.info("Starting BackUpMonitor Thread.. ");
207 TimeUnit.MILLISECONDS.sleep(pingInterval);
209 } catch (Exception e) {
210 LOGGER.error("Error during Thread execution " + e.getMessage(), e);
217 private static BackUpMonitorEntity setMaster(BackUpMonitorEntity bMEntity) {
218 bMEntity.setFlag(MASTER);
224 private static BackUpMonitorEntity setSlave(BackUpMonitorEntity bMEntity) {
225 bMEntity.setFlag(SLAVE);
230 // Check Database and set the Flag.
231 private void checkDataBase() throws BackUpMonitorException {
232 EntityTransaction et = null;
234 et = em.getTransaction();
235 setNotificationRecord();
237 LOGGER.info("Clearing Cache");
238 em.getEntityManagerFactory().getCache().evictAll();
239 LOGGER.info("Checking Datatbase for BackUpMonitor.. ");
241 Query query = em.createQuery("select b from BackUpMonitorEntity b where b.resourceNodeName = :nn");
242 if (resourceNodeName.equals(ResourceNode.ASTRA.toString())) {
243 query.setParameter("nn", ResourceNode.ASTRA.toString());
244 } else if (resourceNodeName.equals(ResourceNode.BRMS.toString())) {
245 query.setParameter("nn", ResourceNode.BRMS.toString());
247 List<?> bMList = query.getResultList();
248 if (bMList.isEmpty()) {
249 // This is New. create an entry as Master.
250 LOGGER.info("Adding resource " + resourceName + " to Database");
251 BackUpMonitorEntity bMEntity = new BackUpMonitorEntity();
252 bMEntity.setResourceNodeName(resourceNodeName);
253 bMEntity.setResourceName(resourceName);
254 bMEntity = setMaster(bMEntity);
255 bMEntity.setTimeStamp(new Date());
256 em.persist(bMEntity);
259 checkOtherMaster(bMList);
262 } catch (Exception e) {
263 LOGGER.error("failed Database Operation " + e.getMessage(), e);
264 if (et!=null && et.isActive()) {
267 throw new BackUpMonitorException(e);
271 private void checkOtherMaster(List<?> bMList) {
272 // Check for other Master(s)
273 ArrayList<BackUpMonitorEntity> masterEntities = new ArrayList<>();
275 BackUpMonitorEntity selfEntity = null;
276 // Check backup monitor entities.
277 for (int i = 0; i < bMList.size(); i++) {
278 BackUpMonitorEntity bMEntity = (BackUpMonitorEntity) bMList.get(i);
279 LOGGER.info("Refreshing Entity. ");
280 em.refresh(bMEntity);
281 if (bMEntity.getFlag().equalsIgnoreCase(MASTER)) {
282 masterEntities.add(bMEntity);
284 if (bMEntity.getResourceName().equals(resourceName)) {
285 selfEntity = bMEntity;
288 if (selfEntity != null) {
289 LOGGER.info("Resource Name already Exists: " + resourceName);
290 if (selfEntity.getFlag().equalsIgnoreCase(MASTER)) {
291 // Already Master Mode.
293 LOGGER.info(resourceName + " is on Master Mode");
294 selfEntity.setTimeStamp(new Date());
295 selfEntity.setNotificationRecord(notificationRecord);
296 em.persist(selfEntity);
298 setLastNotification(null);
299 if (!masterEntities.contains(selfEntity)) {
300 masterEntities.add(selfEntity);
303 // Already Slave Mode.
305 selfEntity.setTimeStamp(new Date());
306 selfEntity.setNotificationRecord(notificationRecord);
307 em.persist(selfEntity);
309 LOGGER.info(resourceName + " is on Slave Mode");
312 // Resource name is null -> No resource with same name.
313 selfEntity = new BackUpMonitorEntity();
314 selfEntity.setResourceNodeName(resourceNodeName);
315 selfEntity.setResourceName(resourceName);
316 selfEntity.setTimeStamp(new Date());
317 selfEntity = setSlave(selfEntity);
318 setLastNotification(null);
319 LOGGER.info("Creating: " + resourceName + " on Slave Mode");
320 em.persist(selfEntity);
323 // Correct the database if any errors and perform monitor checks.
324 if (masterEntities.size() != 1 || !getFlag()) {
325 // We are either not master or there are more masters or no masters.
326 if (masterEntities.isEmpty()) {
327 // No Masters is a problem Convert ourselves to Master.
328 selfEntity = setMaster(selfEntity);
329 selfEntity.setTimeStamp(new Date());
330 selfEntity.setNotificationRecord(notificationRecord);
331 LOGGER.info(resourceName + " changed to Master Mode - No Masters available.");
332 em.persist(selfEntity);
335 if (masterEntities.size() > 1) {
336 masterEntities = multipleMasterEntity(masterEntities);
338 if (masterEntities.size() == 1) {
339 singleMasterEntity(masterEntities, selfEntity);
342 "Backup Monitor Issue, Masters out of sync, This will be fixed in next interval.");
348 private void singleMasterEntity(ArrayList<BackUpMonitorEntity> masterEntities, BackUpMonitorEntity selfEntity) {
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 private ArrayList<BackUpMonitorEntity> multipleMasterEntity(ArrayList<BackUpMonitorEntity> masterEntities) {
379 // More Masters is a problem, Fix the issue by looking for the latest one and make others
381 BackUpMonitorEntity masterEntity = null;
382 for (BackUpMonitorEntity currentEntity : masterEntities) {
383 if (currentEntity.getFlag().equalsIgnoreCase(MASTER)) {
384 if (masterEntity == null) {
385 masterEntity = currentEntity;
386 } else if (currentEntity.getTimeStamp().getTime() > masterEntity.getTimeStamp()
388 // False Master, Update master to slave and take currentMaster as Master.
389 masterEntity.setFlag(SLAVE);
390 masterEntity.setTimeStamp(new Date());
391 em.persist(masterEntity);
393 masterEntity = currentEntity;
395 currentEntity.setFlag(SLAVE);
396 currentEntity.setTimeStamp(new Date());
397 em.persist(currentEntity);
402 masterEntities = new ArrayList<>();
403 masterEntities.add(masterEntity);
404 return masterEntities;
407 private static void setNotificationRecord() throws BackUpMonitorException {
409 notificationRecord = PolicyUtils.objectToJsonString(NotificationStore.getNotificationRecord());
410 } catch (JsonProcessingException e1) {
411 LOGGER.error("Error retrieving notification record failed. ", e1);
412 throw new BackUpMonitorException(e1);
416 // Calculate Patch and return String JsonPatch of the notification Delta.
417 private synchronized String calculatePatch(String oldNotificationRecord) {
419 JsonNode notification = JsonLoader.fromString(notificationRecord);
420 JsonNode oldNotification = JsonLoader.fromString(oldNotificationRecord);
421 JsonNode patchNode = JsonDiff.asJson(oldNotification, notification);
422 LOGGER.info("Generated JSON Patch is " + patchNode.toString());
423 JsonPatch patch = JsonPatch.fromJson(patchNode);
424 return generatePatchNotification(patch, oldNotification);
425 } catch (IOException e) {
426 LOGGER.error("Error generating Patched " + e.getMessage(), e);
431 private String generatePatchNotification(JsonPatch patch, JsonNode oldNotification) {
433 JsonNode patched = patch.apply(oldNotification);
434 LOGGER.info("Generated New Notification is : " + patched.toString());
435 return patched.toString();
436 } catch (JsonPatchException e) {
437 LOGGER.error("Error generating Patched " + e.getMessage(), e);
443 * Updates Notification in the Database while Performing the health check.
445 * @param notification
446 * String format of notification record to store in the Database.
449 public synchronized void updateNotification() throws BackUpMonitorException {
453 // Take in string notification and send the record delta to Handler.
454 private static void callHandler(String notification) {
455 if (handler != null) {
457 PDPNotification notificationObject = PolicyUtils.jsonStringToObject(notification,
458 StdPDPNotification.class);
459 if (notificationObject.getNotificationType() != null) {
460 LOGGER.info("Performing Patched notification ");
461 performPatchNotification(notificationObject);
464 } catch (IOException e) {
465 LOGGER.info("Error while notification Conversion " + e.getMessage(), e);
470 private static void performPatchNotification(PDPNotification notificationObject) {
472 handler.runOnNotification(notificationObject);
473 notificationRecord = lastMasterNotification;
474 } catch (Exception e) {
475 LOGGER.error("Error in Clients Handler Object : " + e.getMessage(), e);
480 // Used to set LastMasterNotification Record.
481 private static void setLastNotification(String notification) {
482 synchronized (notificationLock) {
483 lastMasterNotification = notification;
484 if (lastMasterNotification != null && !"\"notificationType\":null".equals(lastMasterNotification)) {
485 if (lastMasterNotification.equals(notificationRecord)) {
488 callHandler(notification);