2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017, 2019 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 com.fasterxml.jackson.core.JsonProcessingException;
25 import com.fasterxml.jackson.databind.JsonNode;
26 import com.github.fge.jackson.JsonLoader;
27 import com.github.fge.jsonpatch.JsonPatch;
28 import com.github.fge.jsonpatch.JsonPatchException;
29 import com.github.fge.jsonpatch.diff.JsonDiff;
31 import java.io.IOException;
32 import java.util.ArrayList;
33 import java.util.Date;
34 import java.util.List;
35 import java.util.Properties;
36 import java.util.concurrent.TimeUnit;
38 import javax.persistence.EntityManager;
39 import javax.persistence.EntityManagerFactory;
40 import javax.persistence.EntityTransaction;
41 import javax.persistence.Persistence;
42 import javax.persistence.Query;
44 import org.apache.log4j.Logger;
45 import org.eclipse.persistence.config.PersistenceUnitProperties;
46 import org.onap.policy.api.PDPNotification;
47 import org.onap.policy.jpa.BackUpMonitorEntity;
48 import org.onap.policy.std.NotificationStore;
49 import org.onap.policy.std.StdPDPNotification;
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 monitor) {
105 t = new Thread(monitor);
112 * @throws InterruptedException InterruptedException
114 public static void stop() throws InterruptedException {
123 private static void init(String resourceName, String resourceNodeName, BackUpHandler handler) {
124 BackUpMonitor.resourceNodeName = resourceNodeName;
125 BackUpMonitor.resourceName = resourceName;
126 BackUpMonitor.handler = handler;
131 * Gets the BackUpMonitor Instance if given proper resourceName and properties. Else returns null.
133 * @param resourceNodeName
134 * String format of the Resource Node to which the resource Belongs to.
135 * @param resourceName
136 * String format of the ResourceName. This needs to be Unique.
138 * Properties format of the properties file.
139 * @return BackUpMonitor instance.
141 public static synchronized BackUpMonitor getInstance(String resourceNodeName, String resourceName,
142 Properties properties, BackUpHandler handler) throws BackUpMonitorException {
143 if (resourceNodeName == null || "".equals(resourceNodeName.trim()) || resourceName == null
144 || "".equals(resourceName.trim()) || properties == null || handler == null) {
145 LOGGER.error("Error while getting Instance. Please check resourceName and/or properties file");
147 } else if ((resourceNodeName.equals(ResourceNode.ASTRA.toString())
148 || resourceNodeName.equals(ResourceNode.BRMS.toString())) && validate(properties) && instance == null) {
149 LOGGER.info("Creating Instance of BackUpMonitor");
150 instance = new BackUpMonitor(resourceNodeName, resourceName, properties, handler);
155 // This is to validate given Properties with required values.
156 private static Boolean validate(Properties properties) {
157 if (properties.getProperty("javax.persistence.jdbc.driver") == null
158 || "".equals(properties.getProperty("javax.persistence.jdbc.driver").trim())) {
159 LOGGER.error("javax.persistence.jdbc.driver property is empty");
162 if (properties.getProperty("javax.persistence.jdbc.url") == null
163 || "".equals(properties.getProperty("javax.persistence.jdbc.url").trim())) {
164 LOGGER.error("javax.persistence.jdbc.url property is empty");
167 if (properties.getProperty("javax.persistence.jdbc.user") == null
168 || "".equals(properties.getProperty("javax.persistence.jdbc.user").trim())) {
169 LOGGER.error("javax.persistence.jdbc.user property is empty");
172 if (properties.getProperty(PING_INTERVAL) == null || "".equals(properties.getProperty(PING_INTERVAL).trim())) {
173 LOGGER.info("ping_interval property not specified. Taking default value");
176 pingInterval = Integer.parseInt(properties.getProperty(PING_INTERVAL).trim());
177 } catch (NumberFormatException e) {
178 LOGGER.warn("Ignored invalid proeprty ping_interval. Taking default value: " + pingInterval);
179 pingInterval = DEFAULT_PING;
185 // Sets the Flag for masterFlag to either True or False.
186 private static void setFlag(Boolean flag) {
187 synchronized (lock) {
193 * Gets the Boolean value of Master(True) or Slave mode (False).
195 * @return Boolean flag which if True means that the operation needs to be performed(Master mode) or if false the
196 * operation is in slave mode.
198 public Boolean getFlag() {
199 synchronized (lock) {
204 // BackUpMonitor Thread
205 private class BMonitor implements Runnable {
208 LOGGER.info("Starting BackUpMonitor Thread.. ");
211 TimeUnit.MILLISECONDS.sleep(pingInterval);
213 } catch (Exception e) {
214 LOGGER.error("Error during Thread execution " + e.getMessage(), e);
221 private static BackUpMonitorEntity setMaster(BackUpMonitorEntity entity) {
222 entity.setFlag(MASTER);
228 private static BackUpMonitorEntity setSlave(BackUpMonitorEntity entity) {
229 entity.setFlag(SLAVE);
234 // Check Database and set the Flag.
235 private void checkDataBase() throws BackUpMonitorException {
236 EntityTransaction et = null;
238 et = em.getTransaction();
239 setNotificationRecord();
241 LOGGER.info("Clearing Cache");
242 em.getEntityManagerFactory().getCache().evictAll();
243 LOGGER.info("Checking Datatbase for BackUpMonitor.. ");
245 Query query = em.createQuery("select b from BackUpMonitorEntity b where b.resourceNodeName = :nn");
246 if (resourceNodeName.equals(ResourceNode.ASTRA.toString())) {
247 query.setParameter("nn", ResourceNode.ASTRA.toString());
248 } else if (resourceNodeName.equals(ResourceNode.BRMS.toString())) {
249 query.setParameter("nn", ResourceNode.BRMS.toString());
251 List<?> entityList = query.getResultList();
252 if (entityList.isEmpty()) {
253 // This is New. create an entry as Master.
254 LOGGER.info("Adding resource " + resourceName + " to Database");
255 BackUpMonitorEntity entity = new BackUpMonitorEntity();
256 entity.setResourceNodeName(resourceNodeName);
257 entity.setResourceName(resourceName);
258 entity = setMaster(entity);
259 entity.setTimeStamp(new Date());
263 checkOtherMaster(entityList);
266 } catch (Exception e) {
267 LOGGER.error("failed Database Operation " + e.getMessage(), e);
268 if (et != null && et.isActive()) {
271 throw new BackUpMonitorException(e);
275 private void checkOtherMaster(List<?> entityList) {
276 // Check for other Master(s)
277 ArrayList<BackUpMonitorEntity> masterEntities = new ArrayList<>();
279 BackUpMonitorEntity selfEntity = null;
280 // Check backup monitor entities.
281 for (Object entity : entityList) {
282 BackUpMonitorEntity backupEntity = (BackUpMonitorEntity) entity;
283 LOGGER.info("Refreshing Entity. ");
284 em.refresh(backupEntity);
285 if (backupEntity.getFlag().equalsIgnoreCase(MASTER)) {
286 masterEntities.add(backupEntity);
288 if (backupEntity.getResourceName().equals(resourceName)) {
289 selfEntity = backupEntity;
292 if (selfEntity != null) {
293 LOGGER.info("Resource Name already Exists: " + resourceName);
294 if (selfEntity.getFlag().equalsIgnoreCase(MASTER)) {
295 // Already Master Mode.
297 LOGGER.info(resourceName + " is on Master Mode");
298 selfEntity.setTimeStamp(new Date());
299 selfEntity.setNotificationRecord(notificationRecord);
300 em.persist(selfEntity);
302 setLastNotification(null);
303 if (!masterEntities.contains(selfEntity)) {
304 masterEntities.add(selfEntity);
307 // Already Slave Mode.
309 selfEntity.setTimeStamp(new Date());
310 selfEntity.setNotificationRecord(notificationRecord);
311 em.persist(selfEntity);
313 LOGGER.info(resourceName + " is on Slave Mode");
316 // Resource name is null -> No resource with same name.
317 selfEntity = new BackUpMonitorEntity();
318 selfEntity.setResourceNodeName(resourceNodeName);
319 selfEntity.setResourceName(resourceName);
320 selfEntity.setTimeStamp(new Date());
321 selfEntity = setSlave(selfEntity);
322 setLastNotification(null);
323 LOGGER.info("Creating: " + resourceName + " on Slave Mode");
324 em.persist(selfEntity);
327 // Correct the database if any errors and perform monitor checks.
328 if (masterEntities.size() != 1 || !getFlag()) {
329 // We are either not master or there are more masters or no masters.
330 if (masterEntities.isEmpty()) {
331 // No Masters is a problem Convert ourselves to Master.
332 selfEntity = setMaster(selfEntity);
333 selfEntity.setTimeStamp(new Date());
334 selfEntity.setNotificationRecord(notificationRecord);
335 LOGGER.info(resourceName + " changed to Master Mode - No Masters available.");
336 em.persist(selfEntity);
339 if (masterEntities.size() > 1) {
340 masterEntities = multipleMasterEntity(masterEntities);
342 if (masterEntities.size() == 1) {
343 singleMasterEntity(masterEntities, selfEntity);
345 LOGGER.error("Backup Monitor Issue, Masters out of sync, This will be fixed in next interval.");
351 private void singleMasterEntity(ArrayList<BackUpMonitorEntity> masterEntities, BackUpMonitorEntity selfEntity) {
352 // Correct Size, Check if Master is Latest, if not Change Master to Slave and Slave to
354 BackUpMonitorEntity masterEntity = masterEntities.get(0);
355 if (!masterEntity.getResourceName().equals(selfEntity.getResourceName())) {
356 Date currentTime = new Date();
358 timeDiff = currentTime.getTime() - masterEntity.getTimeStamp().getTime();
359 if (timeDiff > (pingInterval + 1500)) {
360 // This is down or has an issue and we need to become Master while turning the
362 masterEntity.setFlag(SLAVE);
363 String lastNotification = null;
364 if (masterEntity.getNotificationRecord() != null) {
365 lastNotification = calculatePatch(masterEntity.getNotificationRecord());
367 setLastNotification(lastNotification);
368 em.persist(masterEntity);
370 // Lets Become Master.
371 LOGGER.info("Changing " + resourceName + " from slave to Master Mode");
372 selfEntity.setTimeStamp(new Date());
373 selfEntity.setNotificationRecord(notificationRecord);
374 em.persist(selfEntity);
380 private ArrayList<BackUpMonitorEntity> multipleMasterEntity(ArrayList<BackUpMonitorEntity> masterEntities) {
381 // More Masters is a problem, Fix the issue by looking for the latest one and make others
383 BackUpMonitorEntity masterEntity = null;
384 for (BackUpMonitorEntity currentEntity : masterEntities) {
385 if (currentEntity.getFlag().equalsIgnoreCase(MASTER)) {
386 if (masterEntity == null) {
387 masterEntity = currentEntity;
388 } else if (currentEntity.getTimeStamp().getTime() > masterEntity.getTimeStamp().getTime()) {
389 // False Master, Update master to slave and take currentMaster as Master.
390 masterEntity.setFlag(SLAVE);
391 masterEntity.setTimeStamp(new Date());
392 em.persist(masterEntity);
394 masterEntity = currentEntity;
396 currentEntity.setFlag(SLAVE);
397 currentEntity.setTimeStamp(new Date());
398 em.persist(currentEntity);
403 masterEntities = new ArrayList<>();
404 masterEntities.add(masterEntity);
405 return masterEntities;
408 private static void setNotificationRecord() throws BackUpMonitorException {
410 notificationRecord = PolicyUtils.objectToJsonString(NotificationStore.getNotificationRecord());
411 } catch (JsonProcessingException e1) {
412 LOGGER.error("Error retrieving notification record failed. ", e1);
413 throw new BackUpMonitorException(e1);
417 // Calculate Patch and return String JsonPatch of the notification Delta.
418 private synchronized String calculatePatch(String oldNotificationRecord) {
420 JsonNode notification = JsonLoader.fromString(notificationRecord);
421 JsonNode oldNotification = JsonLoader.fromString(oldNotificationRecord);
422 JsonNode patchNode = JsonDiff.asJson(oldNotification, notification);
423 LOGGER.info("Generated JSON Patch is " + patchNode.toString());
424 JsonPatch patch = JsonPatch.fromJson(patchNode);
425 return generatePatchNotification(patch, oldNotification);
426 } catch (IOException e) {
427 LOGGER.error("Error generating Patched " + e.getMessage(), e);
432 private String generatePatchNotification(JsonPatch patch, JsonNode oldNotification) {
434 JsonNode patched = patch.apply(oldNotification);
435 LOGGER.info("Generated New Notification is : " + patched.toString());
436 return patched.toString();
437 } catch (JsonPatchException e) {
438 LOGGER.error("Error generating Patched " + e.getMessage(), e);
444 * Updates Notification in the Database while Performing the health check.
446 * @throws BackUpMonitorException BackUpMonitorException
448 public synchronized void updateNotification() throws BackUpMonitorException {
452 // Take in string notification and send the record delta to Handler.
453 private static void callHandler(String notification) {
454 if (handler != null) {
456 PDPNotification notificationObject =
457 PolicyUtils.jsonStringToObject(notification, StdPDPNotification.class);
458 if (notificationObject.getNotificationType() != null) {
459 LOGGER.info("Performing Patched notification ");
460 performPatchNotification(notificationObject);
463 } catch (IOException e) {
464 LOGGER.info("Error while notification Conversion " + e.getMessage(), e);
469 private static void performPatchNotification(PDPNotification notificationObject) {
471 handler.runOnNotification(notificationObject);
472 notificationRecord = lastMasterNotification;
473 } catch (Exception e) {
474 LOGGER.error("Error in Clients Handler Object : " + e.getMessage(), e);
479 // Used to set LastMasterNotification Record.
480 private static void setLastNotification(String notification) {
481 synchronized (notificationLock) {
482 lastMasterNotification = notification;
483 if (lastMasterNotification != null && !"\"notificationType\":null".equals(lastMasterNotification)) {
484 if (lastMasterNotification.equals(notificationRecord)) {
487 callHandler(notification);