1 /*******************************************************************************
2 * ============LICENSE_START==================================================
4 * * ===========================================================================
5 * * Copyright © 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====================================================
20 * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22 ******************************************************************************/
25 package org.onap.dmaap.datarouter.provisioning;
27 import java.io.ByteArrayOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.nio.file.FileStore;
32 import java.nio.file.FileSystem;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 import java.nio.file.StandardCopyOption;
37 import java.util.Properties;
39 import javax.servlet.http.HttpServletRequest;
40 import javax.servlet.http.HttpServletResponse;
42 import org.json.JSONArray;
43 import org.onap.dmaap.datarouter.provisioning.beans.EventLogRecord;
44 import org.onap.dmaap.datarouter.provisioning.beans.LogRecord;
45 import org.onap.dmaap.datarouter.provisioning.beans.Parameters;
46 import org.onap.dmaap.datarouter.provisioning.eelf.EelfMsgs;
47 import org.onap.dmaap.datarouter.provisioning.utils.DB;
48 import org.onap.dmaap.datarouter.provisioning.utils.LogfileLoader;
49 import org.onap.dmaap.datarouter.provisioning.utils.RLEBitSet;
51 import com.att.eelf.configuration.EELFLogger;
52 import com.att.eelf.configuration.EELFManager;
56 * This servlet handles requests to URLs under /internal on the provisioning server.
59 * <div class="contentContainer">
60 * <table class="packageSummary" border="0" cellpadding="3" cellspacing="0">
61 * <caption><span>URL Path Summary</span><span class="tabEnd"> </span></caption>
63 * <th class="colFirst" width="15%">URL Path</th>
64 * <th class="colOne">Method</th>
65 * <th class="colLast">Purpose</th>
67 * <tr class="altColor">
68 * <td class="colFirst">/internal/prov</td>
69 * <td class="colOne">GET</td>
70 * <td class="colLast">used to GET a full JSON copy of the provisioning data.</td>
72 * <tr class="rowColor">
73 * <td class="colFirst">/internal/fetchProv</td>
74 * <td class="colOne">GET</td>
75 * <td class="colLast">used to signal to a standby POD that the provisioning data should be fetched from the active POD.</td>
77 * <tr class="altColor">
78 * <td class="colFirst" rowspan="2">/internal/logs</td>
79 * <td class="colOne">GET</td>
80 * <td class="colLast">used to GET an index of log files and individual logs for this provisioning server.</td>
82 * <tr class="altColor">
83 * <td class="colOne">POST</td>
84 * <td class="colLast">used to POST log files from the individual nodes to this provisioning server.</td>
86 * <tr class="rowColor">
87 * <td class="colFirst" rowspan="4">/internal/api</td>
88 * <td class="colOne">GET</td>
89 * <td class="colLast">used to GET an individual parameter value. The parameter name is specified by the path after /api/.</td>
91 * <tr class="rowColor">
92 * <td class="colOne">PUT</td>
93 * <td class="colLast">used to set an individual parameter value. The parameter name is specified by the path after /api/.</td>
95 * <tr class="rowColor">
96 * <td class="colOne">DELETE</td>
97 * <td class="colLast">used to remove an individual parameter value. The parameter name is specified by the path after /api/.</td>
99 * <tr class="rowColor">
100 * <td class="colOne">POST</td>
101 * <td class="colLast">used to create a new individual parameter value. The parameter name is specified by the path after /api/.</td>
103 * <tr class="altColor">
104 * <td class="colFirst">/internal/halt</td>
105 * <td class="colOne">GET</td>
106 * <td class="colLast">used to halt the server (must be accessed from 127.0.0.1).</td>
108 * <tr class="rowColor">
109 * <td class="colFirst" rowspan="2">/internal/drlogs</td>
110 * <td class="colOne">GET</td>
111 * <td class="colLast">used to get a list of DR log entries available for retrieval.
112 * Note: these are the actual data router log entries sent to the provisioning server
113 * by the nodes, not the provisioning server's internal logs (access via /internal/logs above).
114 * The range is returned as a list of record sequence numbers.</td>
116 * <tr class="rowColor">
117 * <td class="colOne">POST</td>
118 * <td class="colLast">used to retrieve specific log entries.
119 * The sequence numbers of the records to fetch are POST-ed; the records matching the sequence numbers are returned.</td>
121 * <tr class="altColor">
122 * <td class="colFirst">/internal/route/*</td>
123 * <td class="colOne">*</td>
124 * <td class="colLast">URLs under this path are handled via the {@link org.onap.dmaap.datarouter.provisioning.RouteServlet}</td>
129 * Authorization to use these URLs is a little different than for other URLs on the provisioning server.
130 * For the most part, the IP address that the request comes from should be either:
133 * <li>an IP address of a provisioning server, or</li>
134 * <li>the IP address of a node (to allow access to /internal/prov), or</li>
135 * <li>an IP address from the "<i>special subnet</i>" which is configured with
136 * the PROV_SPECIAL_SUBNET parameter.
139 * In addition, requests to /internal/halt can ONLY come from localhost (127.0.0.1) on the HTTP port.
142 * All DELETE/GET/PUT/POST requests made to /internal/api on this servlet on the standby server are
143 * proxied to the active server (using the {@link ProxyServlet}) if it is up and reachable.
147 * @version $Id: InternalServlet.java,v 1.23 2014/03/24 18:47:10 eby Exp $
149 @SuppressWarnings("serial")
150 public class InternalServlet extends ProxyServlet {
151 private static Integer logseq = new Integer(0); // another piece of info to make log spool file names unique
152 //Adding EELF Logger Rally:US664892
153 private static EELFLogger eelflogger = EELFManager.getInstance().getLogger("org.onap.dmaap.datarouter.provisioning.InternalServlet");
156 * Delete a parameter at the address /internal/api/<parameter>.
157 * See the <b>Internal API</b> document for details on how this method should be invoked.
160 public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
161 setIpAndFqdnForEelf("doDelete");
162 eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader(BEHALF_HEADER),getIdFromPath(req)+"");
163 EventLogRecord elr = new EventLogRecord(req);
164 if (!isAuthorizedForInternal(req)) {
165 elr.setMessage("Unauthorized.");
166 elr.setResult(HttpServletResponse.SC_FORBIDDEN);
167 eventlogger.info(elr);
168 resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");
172 String path = req.getPathInfo();
173 if (path.startsWith("/api/")) {
174 if (isProxyOK(req) && isProxyServer()) {
175 super.doDelete(req, resp);
178 String key = path.substring(5);
179 if (key.length() > 0) {
180 Parameters param = Parameters.getParameter(key);
182 if (doDelete(param)) {
183 elr.setResult(HttpServletResponse.SC_OK);
184 eventlogger.info(elr);
185 resp.setStatus(HttpServletResponse.SC_OK);
186 provisioningDataChanged();
187 provisioningParametersChanged();
189 // Something went wrong with the DELETE
190 elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
191 eventlogger.info(elr);
192 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);
198 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");
201 * Get some information (such as a parameter) underneath the /internal/ namespace.
202 * See the <b>Internal API</b> document for details on how this method should be invoked.
205 public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
206 setIpAndFqdnForEelf("doGet");
207 eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader(BEHALF_HEADER),getIdFromPath(req)+"");
208 String path = req.getPathInfo();
209 if (path.equals("/halt") && !req.isSecure()) {
210 // request to halt the server - can ONLY come from localhost
211 String remote = req.getRemoteAddr();
212 if (remote.equals("127.0.0.1")) {
213 intlogger.info("PROV0009 Request to HALT received.");
214 resp.setStatus(HttpServletResponse.SC_OK);
217 intlogger.info("PROV0010 Disallowed request to HALT received from "+remote);
218 resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
223 EventLogRecord elr = new EventLogRecord(req);
224 if (!isAuthorizedForInternal(req)) {
225 elr.setMessage("Unauthorized.");
226 elr.setResult(HttpServletResponse.SC_FORBIDDEN);
227 eventlogger.info(elr);
228 resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");
231 if (path.equals("/fetchProv") && !req.isSecure()) {
232 // if request came from active_pod or standby_pod and it is not us, reload prov data
233 SynchronizerTask s = SynchronizerTask.getSynchronizer();
235 resp.setStatus(HttpServletResponse.SC_OK);
238 if (path.equals("/prov")) {
239 if (isProxyOK(req) && isProxyServer()) {
240 if (super.doGetWithFallback(req, resp))
242 // fall back to returning the local data if the remote is unreachable
243 intlogger.info("Active server unavailable; falling back to local copy.");
245 Poker p = Poker.getPoker();
246 resp.setStatus(HttpServletResponse.SC_OK);
247 resp.setContentType(PROVFULL_CONTENT_TYPE2);
248 resp.getOutputStream().print(p.getProvisioningString());
251 if (path.equals("/logs") || path.equals("/logs/")) {
252 resp.setStatus(HttpServletResponse.SC_OK);
253 resp.setContentType("application/json");
254 resp.getOutputStream().print(generateLogfileList().toString());
257 if (path.startsWith("/logs/")) {
258 Properties p = (new DB()).getProperties();
259 String logdir = p.getProperty("org.onap.dmaap.datarouter.provserver.accesslog.dir");
260 String logfile = path.substring(6);
261 if (logdir != null && logfile != null && logfile.indexOf('/') < 0) {
262 File log = new File(logdir + "/" + logfile);
263 if (log.exists() && log.isFile()) {
264 resp.setStatus(HttpServletResponse.SC_OK);
265 resp.setContentType("text/plain");
266 Path logpath = Paths.get(log.getAbsolutePath());
267 Files.copy(logpath, resp.getOutputStream());
271 resp.sendError(HttpServletResponse.SC_NO_CONTENT, "No file.");
274 if (path.startsWith("/api/")) {
275 if (isProxyOK(req) && isProxyServer()) {
276 super.doGet(req, resp);
279 String key = path.substring(5);
280 if (key.length() > 0) {
281 Parameters param = Parameters.getParameter(key);
283 resp.setStatus(HttpServletResponse.SC_OK);
284 resp.setContentType("text/plain");
285 resp.getOutputStream().print(param.getValue() + "\n");
290 if (path.equals("/drlogs") || path.equals("/drlogs/")) {
291 // Special POD <=> POD API to determine what log file records are loaded here
292 LogfileLoader lfl = LogfileLoader.getLoader();
293 resp.setStatus(HttpServletResponse.SC_OK);
294 resp.setContentType("text/plain");
295 resp.getOutputStream().print(lfl.getBitSet().toString());
298 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");
301 * Modify a parameter at the address /internal/api/<parameter>.
302 * See the <b>Internal API</b> document for details on how this method should be invoked.
305 public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
306 setIpAndFqdnForEelf("doPut");
307 eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader(BEHALF_HEADER),getIdFromPath(req)+"");
308 EventLogRecord elr = new EventLogRecord(req);
309 if (!isAuthorizedForInternal(req)) {
310 elr.setMessage("Unauthorized.");
311 elr.setResult(HttpServletResponse.SC_FORBIDDEN);
312 eventlogger.info(elr);
313 resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");
316 String path = req.getPathInfo();
317 if (path.startsWith("/api/")) {
318 if (isProxyOK(req) && isProxyServer()) {
319 super.doPut(req, resp);
322 String key = path.substring(5);
323 if (key.length() > 0) {
324 Parameters param = Parameters.getParameter(key);
326 String t = catValues(req.getParameterValues("val"));
328 if (doUpdate(param)) {
329 elr.setResult(HttpServletResponse.SC_OK);
330 eventlogger.info(elr);
331 resp.setStatus(HttpServletResponse.SC_OK);
332 provisioningDataChanged();
333 provisioningParametersChanged();
335 // Something went wrong with the UPDATE
336 elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
337 eventlogger.info(elr);
338 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);
344 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");
347 * Create some new information (such as a parameter or log entries) underneath the /internal/ namespace.
348 * See the <b>Internal API</b> document for details on how this method should be invoked.
350 @SuppressWarnings("resource")
352 public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
353 setIpAndFqdnForEelf("doPost");
354 eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF, req.getHeader(BEHALF_HEADER));
355 EventLogRecord elr = new EventLogRecord(req);
356 if (!isAuthorizedForInternal(req)) {
357 elr.setMessage("Unauthorized.");
358 elr.setResult(HttpServletResponse.SC_FORBIDDEN);
359 eventlogger.info(elr);
360 resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");
364 String path = req.getPathInfo();
365 if (path.startsWith("/api/")) {
366 if (isProxyOK(req) && isProxyServer()) {
367 super.doPost(req, resp);
370 String key = path.substring(5);
371 if (key.length() > 0) {
372 Parameters param = Parameters.getParameter(key);
374 String t = catValues(req.getParameterValues("val"));
375 param = new Parameters(key, t);
376 if (doInsert(param)) {
377 elr.setResult(HttpServletResponse.SC_OK);
378 eventlogger.info(elr);
379 resp.setStatus(HttpServletResponse.SC_OK);
380 provisioningDataChanged();
381 provisioningParametersChanged();
383 // Something went wrong with the INSERT
384 elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
385 eventlogger.info(elr);
386 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);
393 if (path.equals("/logs") || path.equals("/logs/")) {
394 String ctype = req.getHeader("Content-Type");
395 if (ctype == null || !ctype.equals("text/plain")) {
396 elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
397 elr.setMessage("Bad media type: "+ctype);
398 resp.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
399 eventlogger.info(elr);
402 String spooldir = (new DB()).getProperties().getProperty("org.onap.dmaap.datarouter.provserver.spooldir");
403 String spoolname = String.format("%d-%d-", System.currentTimeMillis(), Thread.currentThread().getId());
404 synchronized (logseq) {
405 // perhaps unnecessary, but it helps make the name unique
406 spoolname += logseq.toString();
409 String encoding = req.getHeader("Content-Encoding");
410 if (encoding != null) {
411 if (encoding.trim().equals("gzip")) {
414 elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
415 resp.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
416 eventlogger.info(elr);
420 // Determine space available -- available space must be at least 5%
421 FileSystem fs = (Paths.get(spooldir)).getFileSystem();
424 for (FileStore store: fs.getFileStores()) {
425 total += store.getTotalSpace();
426 avail += store.getUsableSpace();
428 try { fs.close(); } catch (Exception e) { }
429 if (((avail * 100) / total) < 5) {
430 elr.setResult(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
431 resp.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
432 eventlogger.info(elr);
435 Path tmppath = Paths.get(spooldir, spoolname);
436 Path donepath = Paths.get(spooldir, "IN."+spoolname);
437 Files.copy(req.getInputStream(), Paths.get(spooldir, spoolname), StandardCopyOption.REPLACE_EXISTING);
438 Files.move(tmppath, donepath, StandardCopyOption.REPLACE_EXISTING);
439 elr.setResult(HttpServletResponse.SC_CREATED);
440 resp.setStatus(HttpServletResponse.SC_CREATED);
441 eventlogger.info(elr);
442 LogfileLoader.getLoader(); // This starts the logfile loader "task"
446 if (path.equals("/drlogs") || path.equals("/drlogs/")) {
447 // Receive post request and generate log entries
448 String ctype = req.getHeader("Content-Type");
449 if (ctype == null || !ctype.equals("text/plain")) {
450 elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
451 elr.setMessage("Bad media type: "+ctype);
452 resp.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
453 eventlogger.info(elr);
456 InputStream is = req.getInputStream();
457 ByteArrayOutputStream bos = new ByteArrayOutputStream();
459 while ((ch = is.read()) >= 0)
461 RLEBitSet bs = new RLEBitSet(bos.toString()); // The set of records to retrieve
462 elr.setResult(HttpServletResponse.SC_OK);
463 resp.setStatus(HttpServletResponse.SC_OK);
464 resp.setContentType("text/plain");
465 LogRecord.printLogRecords(resp.getOutputStream(), bs);
466 eventlogger.info(elr);
470 elr.setResult(HttpServletResponse.SC_NOT_FOUND);
471 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");
472 eventlogger.info(elr);
475 private String catValues(String[] v) {
476 StringBuilder sb = new StringBuilder();
485 return sb.toString();
487 private JSONArray generateLogfileList() {
488 JSONArray ja = new JSONArray();
489 Properties p = (new DB()).getProperties();
490 String s = p.getProperty("org.onap.dmaap.datarouter.provserver.accesslog.dir");
492 String[] dirs = s.split(",");
493 for (String dir : dirs) {
494 File f = new File(dir);
495 String[] list = f.list();
497 for (String s2 : list) {
498 if (!s2.startsWith("."))