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 = 15000; // Value is in milliseconds.
58 private static BackUpMonitor instance = null;
59 private static String resourceName = null;
60 private static String resourceNodeName = null;
61 private static String notificationRecord = null;
62 private static String lastMasterNotification = null;
63 private static int pingInterval = DEFAULT_PING;
64 private static Boolean masterFlag = false;
65 private static Object lock = new Object();
66 private static Object notificationLock = new Object();
67 private static BackUpHandler handler = null;
68 private EntityManager em;
69 private EntityManagerFactory emf;
72 * Enumeration for the Resource Node Naming. Add here if required.
74 public enum ResourceNode {
78 private BackUpMonitor(String resourceNodeName, String resourceName, Properties properties, BackUpHandler handler)
79 throws BackUpMonitorException {
80 if (instance == null) {
83 BackUpMonitor.resourceNodeName = resourceNodeName;
84 BackUpMonitor.resourceName = resourceName;
85 BackUpMonitor.handler = handler;
86 // Create Persistence Entity
87 properties.setProperty(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML, "META-INF/persistencePU.xml");
88 emf = Persistence.createEntityManagerFactory("PolicyEngineUtils", properties);
90 LOGGER.error("Unable to create Entity Manger Factory ");
91 throw new BackUpMonitorException("Unable to create Entity Manger Factory");
93 em = emf.createEntityManager();
95 // Check Database if this is Master or Slave.
99 Thread t = new Thread(new BMonitor());
104 * Gets the BackUpMonitor Instance if given proper resourceName and properties. Else returns null.
106 * @param resourceNodeName
107 * String format of the Resource Node to which the resource Belongs to.
108 * @param resourceName
109 * String format of the ResourceName. This needs to be Unique.
111 * Properties format of the properties file.
112 * @return BackUpMonitor instance.
114 public static synchronized BackUpMonitor getInstance(String resourceNodeName, String resourceName,
115 Properties properties, BackUpHandler handler) throws BackUpMonitorException {
116 if (resourceNodeName == null || resourceNodeName.trim().equals("") || resourceName == null
117 || resourceName.trim().equals("") || properties == null || handler == null) {
118 LOGGER.error("Error while getting Instance. Please check resourceName and/or properties file");
120 } else if ((resourceNodeName.equals(ResourceNode.ASTRA.toString())
121 || resourceNodeName.equals(ResourceNode.BRMS.toString())) && validate(properties) && instance == null) {
122 LOGGER.info("Creating Instance of BackUpMonitor");
123 instance = new BackUpMonitor(resourceNodeName, resourceName, properties, handler);
128 // This is to validate given Properties with required values.
129 private static Boolean validate(Properties properties) {
130 if (properties.getProperty("javax.persistence.jdbc.driver") == null
131 || properties.getProperty("javax.persistence.jdbc.driver").trim().equals("")) {
132 LOGGER.error("javax.persistence.jdbc.driver property is empty");
135 if (properties.getProperty("javax.persistence.jdbc.url") == null
136 || properties.getProperty("javax.persistence.jdbc.url").trim().equals("")) {
137 LOGGER.error("javax.persistence.jdbc.url property is empty");
140 if (properties.getProperty("javax.persistence.jdbc.user") == null
141 || properties.getProperty("javax.persistence.jdbc.user").trim().equals("")) {
142 LOGGER.error("javax.persistence.jdbc.user property is empty");
145 if (properties.getProperty("javax.persistence.jdbc.password") == null
146 || properties.getProperty("javax.persistence.jdbc.password").trim().equals("")) {
147 LOGGER.error("javax.persistence.jdbc.password property is empty");
150 if (properties.getProperty("ping_interval") == null
151 || properties.getProperty("ping_interval").trim().equals("")) {
152 LOGGER.info("ping_interval property not specified. Taking default value");
155 pingInterval = Integer.parseInt(properties.getProperty("ping_interval").trim());
156 } catch (NumberFormatException e) {
157 LOGGER.warn("Ignored invalid proeprty ping_interval. Taking default value: " + pingInterval);
158 pingInterval = DEFAULT_PING;
164 // Sets the Flag for masterFlag to either True or False.
165 private static void setFlag(Boolean flag) {
166 synchronized (lock) {
172 * Gets the Boolean value of Master(True) or Slave mode (False)
174 * @return Boolean flag which if True means that the operation needs to be performed(Master mode) or if false the
175 * operation is in slave mode.
177 public Boolean getFlag() {
178 synchronized (lock) {
183 // BackUpMonitor Thread
184 private class BMonitor implements Runnable {
187 LOGGER.info("Starting BackUpMonitor Thread.. ");
190 TimeUnit.MILLISECONDS.sleep(pingInterval);
192 } catch (Exception e) {
193 LOGGER.error("Error during Thread execution " + e.getMessage(), e);
200 private static BackUpMonitorEntity setMaster(BackUpMonitorEntity bMEntity) {
201 bMEntity.setFlag("MASTER");
207 private static BackUpMonitorEntity setSlave(BackUpMonitorEntity bMEntity) {
208 bMEntity.setFlag("SLAVE");
213 // Check Database and set the Flag.
214 private void checkDataBase() throws BackUpMonitorException {
215 EntityTransaction et = em.getTransaction();
216 setNotificationRecord();
218 LOGGER.info("Clearing Cache");
219 em.getEntityManagerFactory().getCache().evictAll();
221 LOGGER.info("Checking Datatbase for BackUpMonitor.. ");
223 Query query = em.createQuery("select b from BackUpMonitorEntity b where b.resourceNodeName = :nn");
224 if (resourceNodeName.equals(ResourceNode.ASTRA.toString())) {
225 query.setParameter("nn", ResourceNode.ASTRA.toString());
226 } else if (resourceNodeName.equals(ResourceNode.BRMS.toString())) {
227 query.setParameter("nn", ResourceNode.BRMS.toString());
229 List<?> bMList = query.getResultList();
230 if (bMList.isEmpty()) {
231 // This is New. create an entry as Master.
232 LOGGER.info("Adding resource " + resourceName + " to Database");
233 BackUpMonitorEntity bMEntity = new BackUpMonitorEntity();
234 bMEntity.setResoruceNodeName(resourceNodeName);
235 bMEntity.setResourceName(resourceName);
236 bMEntity = setMaster(bMEntity);
237 bMEntity.setTimeStamp(new Date());
238 em.persist(bMEntity);
241 // Check for other Master(s)
242 ArrayList<BackUpMonitorEntity> masterEntities = new ArrayList<>();
244 BackUpMonitorEntity selfEntity = null;
245 // Check backup monitor entities.
246 for (int i = 0; i < bMList.size(); i++) {
247 BackUpMonitorEntity bMEntity = (BackUpMonitorEntity) bMList.get(i);
248 LOGGER.info("Refreshing Entity. ");
249 em.refresh(bMEntity);
250 if (bMEntity.getFlag().equalsIgnoreCase("MASTER")) {
251 masterEntities.add(bMEntity);
253 if (bMEntity.getResourceName().equals(resourceName)) {
254 selfEntity = bMEntity;
257 if (selfEntity != null) {
258 LOGGER.info("Resource Name already Exists: " + resourceName);
259 if (selfEntity.getFlag().equalsIgnoreCase("MASTER")) {
260 // Already Master Mode.
262 LOGGER.info(resourceName + " is on Master Mode");
263 selfEntity.setTimeStamp(new Date());
264 selfEntity.setNotificationRecord(notificationRecord);
265 em.persist(selfEntity);
267 setLastNotification(null);
268 if (!masterEntities.contains(selfEntity)) {
269 masterEntities.add(selfEntity);
272 // Already Slave Mode.
274 selfEntity.setTimeStamp(new Date());
275 selfEntity.setNotificationRecord(notificationRecord);
276 em.persist(selfEntity);
278 LOGGER.info(resourceName + " is on Slave Mode");
281 // Resource name is null -> No resource with same name.
282 selfEntity = new BackUpMonitorEntity();
283 selfEntity.setResoruceNodeName(resourceNodeName);
284 selfEntity.setResourceName(resourceName);
285 selfEntity.setTimeStamp(new Date());
286 selfEntity = setSlave(selfEntity);
287 setLastNotification(null);
288 LOGGER.info("Creating: " + resourceName + " on Slave Mode");
289 em.persist(selfEntity);
292 // Correct the database if any errors and perform monitor checks.
293 if (masterEntities.size() != 1 || !getFlag()) {
294 // We are either not master or there are more masters or no masters.
295 if (masterEntities.isEmpty()) {
296 // No Masters is a problem Convert ourselves to Master.
297 selfEntity = setMaster(selfEntity);
298 selfEntity.setTimeStamp(new Date());
299 selfEntity.setNotificationRecord(notificationRecord);
300 LOGGER.info(resourceName + " changed to Master Mode - No Masters available.");
301 em.persist(selfEntity);
304 if (masterEntities.size() > 1) {
305 // More Masters is a problem, Fix the issue by looking for the latest one and make others
307 BackUpMonitorEntity masterEntity = null;
308 for (BackUpMonitorEntity currentEntity : masterEntities) {
309 if (currentEntity.getFlag().equalsIgnoreCase("MASTER")) {
310 if (masterEntity == null) {
311 masterEntity = currentEntity;
312 } else if (currentEntity.getTimeStamp().getTime() > masterEntity.getTimeStamp()
314 // False Master, Update master to slave and take currentMaster as Master.
315 masterEntity.setFlag("SLAVE");
316 masterEntity.setTimeStamp(new Date());
317 em.persist(masterEntity);
319 masterEntity = currentEntity;
321 currentEntity.setFlag("SLAVE");
322 currentEntity.setTimeStamp(new Date());
323 em.persist(currentEntity);
328 masterEntities = new ArrayList<>();
329 masterEntities.add(masterEntity);
331 if (masterEntities.size() == 1) {
332 // Correct Size, Check if Master is Latest, if not Change Master to Slave and Slave to
334 BackUpMonitorEntity masterEntity = masterEntities.get(0);
335 if (!masterEntity.getResourceName().equals(selfEntity.getResourceName())) {
336 Date currentTime = new Date();
338 timeDiff = currentTime.getTime() - masterEntity.getTimeStamp().getTime();
339 if (timeDiff > (pingInterval + 1500)) {
340 // This is down or has an issue and we need to become Master while turning the
342 masterEntity.setFlag("SLAVE");
343 String lastNotification = null;
344 if (masterEntity.getNotificationRecord() != null) {
345 lastNotification = calculatePatch(masterEntity.getNotificationRecord());
347 setLastNotification(lastNotification);
348 em.persist(masterEntity);
350 // Lets Become Master.
351 selfEntity = setMaster(selfEntity);
352 LOGGER.info("Changing " + resourceName + " from slave to Master Mode");
353 selfEntity.setTimeStamp(new Date());
354 selfEntity.setNotificationRecord(notificationRecord);
355 em.persist(selfEntity);
361 "Backup Monitor Issue, Masters out of sync, This will be fixed in next interval.");
367 } catch (Exception e) {
368 LOGGER.error("failed Database Operation " + e.getMessage(), e);
372 throw new BackUpMonitorException(e);
376 private static void setNotificationRecord() throws BackUpMonitorException {
378 notificationRecord = PolicyUtils.objectToJsonString(NotificationStore.getNotificationRecord());
379 } catch (JsonProcessingException e1) {
380 LOGGER.error("Error retrieving notification record failed. ", e1);
381 throw new BackUpMonitorException(e1);
385 // Calculate Patch and return String JsonPatch of the notification Delta.
386 private synchronized String calculatePatch(String oldNotificationRecord) {
388 JsonNode notification = JsonLoader.fromString(notificationRecord);
389 JsonNode oldNotification = JsonLoader.fromString(oldNotificationRecord);
390 JsonNode patchNode = JsonDiff.asJson(oldNotification, notification);
391 LOGGER.info("Generated JSON Patch is " + patchNode.toString());
392 JsonPatch patch = JsonPatch.fromJson(patchNode);
394 JsonNode patched = patch.apply(oldNotification);
395 LOGGER.info("Generated New Notification is : " + patched.toString());
396 return patched.toString();
397 } catch (JsonPatchException e) {
398 LOGGER.error("Error generating Patched " + e.getMessage(), e);
401 } catch (IOException e) {
402 LOGGER.error("Error generating Patched " + e.getMessage(), e);
408 * Updates Notification in the Database while Performing the health check.
410 * @param notification
411 * String format of notification record to store in the Database.
414 public synchronized void updateNotification() throws BackUpMonitorException {
418 // Take in string notification and send the record delta to Handler.
419 private static void callHandler(String notification) {
420 if (handler != null) {
422 PDPNotification notificationObject = PolicyUtils.jsonStringToObject(notification,
423 StdPDPNotification.class);
424 if (notificationObject.getNotificationType() != null) {
425 LOGGER.info("Performing Patched notification ");
427 handler.runOnNotification(notificationObject);
428 notificationRecord = lastMasterNotification;
429 } catch (Exception e) {
430 LOGGER.error("Error in Clients Handler Object : " + e.getMessage(), e);
433 } catch (IOException e) {
434 LOGGER.info("Error while notification Conversion " + e.getMessage(), e);
439 // Used to set LastMasterNotification Record.
440 private static void setLastNotification(String notification) {
441 synchronized (notificationLock) {
442 lastMasterNotification = notification;
443 if (lastMasterNotification != null && !lastMasterNotification.equals("\"notificationType\":null")) {
444 if (lastMasterNotification.equals(notificationRecord)) {
447 callHandler(notification);