2687799cc66ccc7afce989ce8a2ed26597dcd615
[policy/engine.git] / ONAP-PAP-REST / src / main / java / org / onap / policy / pap / xacml / rest / Heartbeat.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP-PAP-REST
4  * ================================================================================
5  * Copyright (C) 2019 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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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=========================================================
19  */
20
21 package org.onap.policy.pap.xacml.rest;
22
23 import com.att.research.xacml.api.pap.PAPException;
24 import com.att.research.xacml.api.pap.PDPStatus;
25 import com.att.research.xacml.util.XACMLProperties;
26 import java.net.ConnectException;
27 import java.net.HttpURLConnection;
28 import java.net.MalformedURLException;
29 import java.net.SocketTimeoutException;
30 import java.net.URL;
31 import java.net.UnknownHostException;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Set;
35 import org.onap.policy.common.logging.eelf.MessageCodes;
36 import org.onap.policy.common.logging.eelf.PolicyLogger;
37 import org.onap.policy.common.logging.flexlogger.FlexLogger;
38 import org.onap.policy.common.logging.flexlogger.Logger;
39 import org.onap.policy.pap.xacml.restAuth.CheckPDP;
40 import org.onap.policy.rest.XACMLRestProperties;
41 import org.onap.policy.xacml.api.pap.OnapPDP;
42 import org.onap.policy.xacml.api.pap.OnapPDPGroup;
43 import org.onap.policy.xacml.api.pap.PAPPolicyEngine;
44
45
46 /**
47  * Heartbeat thread - periodically check on PDPs' status.
48  * Heartbeat with all known PDPs.
49  * Implementation note:
50  * The PDPs are contacted Sequentially, not in Parallel.
51  * If we did this in parallel using multiple threads we would simultaneously use - 1 thread and - 1
52  * connection for EACH PDP. This could become a resource problem since we already use multiple
53  * threads and connections for updating the PDPs when user changes occur. Using separate threads can
54  * also make it tricky dealing with timeouts on PDPs that are non-responsive.
55  * The Sequential operation does a heartbeat request to each PDP one at a time. This has the flaw
56  * that any PDPs that do not respond will hold up the entire heartbeat sequence until they timeout.
57  * If there are a lot of non-responsive PDPs and the timeout is large-ish (the default is 20
58  * seconds) it could take a long time to cycle through all of the PDPs. That means that this may not
59  * notice a PDP being down in a predictable time.
60  */
61 public class Heartbeat implements Runnable {
62
63     private static final Logger LOGGER = FlexLogger.getLogger(Heartbeat.class);
64
65     private PAPPolicyEngine papEngine;
66     private Set<OnapPDP> pdps = new HashSet<>();
67     private int heartbeatInterval;
68     private int heartbeatTimeout;
69     private static final String HEARTBEATSTRING = " Heartbeat '";
70     private static final String XACMLPAPSERVLET = "XacmlPapServlet";
71     private static final String ISRUNNINGFALSE = "isRunning is false, getting out of loop.";
72     private volatile boolean isRunning = false;
73
74     public synchronized boolean isHeartBeatRunning() {
75         return this.isRunning;
76     }
77
78     public synchronized void terminate() {
79         this.isRunning = false;
80     }
81
82     /**
83      * Instantiates a new heartbeat.
84      *
85      * @param papEngine2 the pap engine 2
86      */
87     public Heartbeat(PAPPolicyEngine papEngine2) {
88         papEngine = papEngine2;
89         this.heartbeatInterval =
90                 Integer.parseInt(XACMLProperties.getProperty(XACMLRestProperties.PROP_PAP_HEARTBEAT_INTERVAL, "10000"));
91         this.heartbeatTimeout =
92                 Integer.parseInt(XACMLProperties.getProperty(XACMLRestProperties.PROP_PAP_HEARTBEAT_TIMEOUT, "10000"));
93     }
94
95     @Override
96     public void run() {
97         // Set ourselves as running
98         synchronized (this) {
99             this.isRunning = true;
100         }
101         try {
102             while (this.isHeartBeatRunning()) {
103                 // Wait the given time
104                 Thread.sleep(heartbeatInterval);
105                 // get the list of PDPs (may have changed since last time)
106                 pdps.clear();
107                 synchronized (papEngine) {
108                     getPdpsFromGroup();
109                 }
110                 // Check for shutdown
111                 if (!this.isHeartBeatRunning()) {
112                     LOGGER.info(ISRUNNINGFALSE);
113                     break;
114                 }
115                 notifyEachPdp();
116                 // Check for shutdown
117                 if (!this.isHeartBeatRunning()) {
118                     LOGGER.info(ISRUNNINGFALSE);
119                     break;
120                 }
121             }
122         } catch (InterruptedException e) {
123             PolicyLogger.error(MessageCodes.ERROR_SYSTEM_ERROR + " Heartbeat interrupted.  Shutting down");
124             this.terminate();
125             Thread.currentThread().interrupt();
126         }
127     }
128
129     private void getPdpsFromGroup() {
130         try {
131             for (OnapPDPGroup g : papEngine.getOnapPDPGroups()) {
132                 for (OnapPDP p : g.getOnapPdps()) {
133                     pdps.add(p);
134                 }
135             }
136         } catch (PAPException e) {
137             PolicyLogger.error(MessageCodes.ERROR_SYSTEM_ERROR, e, XACMLPAPSERVLET,
138                     "Heartbeat unable to read PDPs from PAPEngine");
139         }
140     }
141
142     private void notifyEachPdp() {
143         HashMap<String, URL> idToUrlMap = new HashMap<>();
144         for (OnapPDP pdp : pdps) {
145             // Check for shutdown
146             if (!this.isHeartBeatRunning()) {
147                 LOGGER.info(ISRUNNINGFALSE);
148                 break;
149             }
150             // the id of the PDP is its url (though we add a query
151             // parameter)
152             URL pdpUrl = idToUrlMap.get(pdp.getId());
153             if (pdpUrl == null) {
154                 // haven't seen this PDP before
155                 String fullUrlString = null;
156                 try {
157                     // Check PDP ID
158                     if (CheckPDP.validateID(pdp.getId())) {
159                         fullUrlString = pdp.getId() + "?type=hb";
160                         pdpUrl = new URL(fullUrlString);
161                         idToUrlMap.put(pdp.getId(), pdpUrl);
162                     }
163                 } catch (MalformedURLException e) {
164                     PolicyLogger.error(MessageCodes.ERROR_DATA_ISSUE, e, XACMLPAPSERVLET,
165                             " PDP id '" + fullUrlString + "' is not a valid URL");
166                 }
167             }
168             updatePdpStatus(pdp, openPdpConnection(pdpUrl, pdp));
169         }
170     }
171
172     private String openPdpConnection(URL pdpUrl, OnapPDP pdp) {
173         // Do a GET with type HeartBeat
174         String newStatus = "";
175         HttpURLConnection connection = null;
176         try {
177             // Open up the connection
178             if (pdpUrl != null) {
179                 connection = (HttpURLConnection) pdpUrl.openConnection();
180                 // Setup our method and headers
181                 connection.setRequestMethod("GET");
182                 connection.setConnectTimeout(heartbeatTimeout);
183                 // Authentication
184                 String encoding = CheckPDP.getEncoding(pdp.getId());
185                 if (encoding != null) {
186                     connection.setRequestProperty("Authorization", "Basic " + encoding);
187                 }
188                 // Do the connect
189                 connection.connect();
190                 if (connection.getResponseCode() == 204) {
191                     newStatus = connection.getHeaderField(XACMLRestProperties.PROP_PDP_HTTP_HEADER_HB);
192                     if (LOGGER.isDebugEnabled()) {
193                         LOGGER.debug("Heartbeat '" + pdp.getId() + "' status='" + newStatus + "'");
194                     }
195                 } else {
196                     // anything else is an unexpected result
197                     newStatus = PDPStatus.Status.UNKNOWN.toString();
198                     PolicyLogger.error(MessageCodes.ERROR_SYSTEM_ERROR + " Heartbeat connect response code "
199                             + connection.getResponseCode() + ": " + pdp.getId());
200                 }
201             }
202         } catch (UnknownHostException e) {
203             newStatus = PDPStatus.Status.NO_SUCH_HOST.toString();
204             PolicyLogger.error(MessageCodes.ERROR_SYSTEM_ERROR, e, XACMLPAPSERVLET,
205                     HEARTBEATSTRING + pdp.getId() + "' NO_SUCH_HOST");
206         } catch (SocketTimeoutException e) {
207             newStatus = PDPStatus.Status.CANNOT_CONNECT.toString();
208             PolicyLogger.error(MessageCodes.ERROR_SYSTEM_ERROR, e, XACMLPAPSERVLET,
209                     HEARTBEATSTRING + pdp.getId() + "' connection timeout");
210         } catch (ConnectException e) {
211             newStatus = PDPStatus.Status.CANNOT_CONNECT.toString();
212             PolicyLogger.error(MessageCodes.ERROR_SYSTEM_ERROR, e, XACMLPAPSERVLET,
213                     HEARTBEATSTRING + pdp.getId() + "' cannot connect");
214         } catch (Exception e) {
215             newStatus = PDPStatus.Status.UNKNOWN.toString();
216             PolicyLogger.error(MessageCodes.ERROR_SYSTEM_ERROR, e, XACMLPAPSERVLET,
217                     HEARTBEATSTRING + pdp.getId() + "' connect exception");
218         } finally {
219             // cleanup the connection
220             if (connection != null)
221                 connection.disconnect();
222         }
223         return newStatus;
224     }
225
226     private void updatePdpStatus(OnapPDP pdp, String newStatus) {
227         if (!pdp.getStatus().getStatus().toString().equals(newStatus)) {
228             if (LOGGER.isDebugEnabled()) {
229                 LOGGER.debug("previous status='" + pdp.getStatus().getStatus() + "'  new Status='" + newStatus + "'");
230             }
231             try {
232                 getPAPInstance().setPDPSummaryStatus(pdp, newStatus);
233             } catch (PAPException e) {
234                 PolicyLogger.error(MessageCodes.ERROR_PROCESS_FLOW, e, XACMLPAPSERVLET,
235                         "Unable to set state for PDP '" + pdp.getId());
236             }
237         }
238     }
239
240     private XACMLPapServlet getPAPInstance() {
241         return new XACMLPapServlet();
242     }
243
244 }