--- /dev/null
+/*******************************************************************************\r
+ * ============LICENSE_START==================================================\r
+ * * org.onap.dmaap\r
+ * * ===========================================================================\r
+ * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.\r
+ * * ===========================================================================\r
+ * * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * * you may not use this file except in compliance with the License.\r
+ * * You may obtain a copy of the License at\r
+ * * \r
+ * * http://www.apache.org/licenses/LICENSE-2.0\r
+ * * \r
+ * * Unless required by applicable law or agreed to in writing, software\r
+ * * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * * See the License for the specific language governing permissions and\r
+ * * limitations under the License.\r
+ * * ============LICENSE_END====================================================\r
+ * *\r
+ * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
+ * *\r
+ ******************************************************************************/\r
+\r
+\r
+package com.att.research.datarouter.provisioning;\r
+\r
+import java.io.ByteArrayOutputStream;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.nio.file.FileStore;\r
+import java.nio.file.FileSystem;\r
+import java.nio.file.Files;\r
+import java.nio.file.Path;\r
+import java.nio.file.Paths;\r
+import java.nio.file.StandardCopyOption;\r
+import java.util.Properties;\r
+\r
+import javax.servlet.http.HttpServletRequest;\r
+import javax.servlet.http.HttpServletResponse;\r
+\r
+import org.json.JSONArray;\r
+\r
+import com.att.eelf.configuration.EELFLogger;\r
+import com.att.eelf.configuration.EELFManager;\r
+import com.att.research.datarouter.provisioning.beans.EventLogRecord;\r
+import com.att.research.datarouter.provisioning.beans.LogRecord;\r
+import com.att.research.datarouter.provisioning.beans.Parameters;\r
+import com.att.research.datarouter.provisioning.eelf.EelfMsgs;\r
+import com.att.research.datarouter.provisioning.utils.DB;\r
+import com.att.research.datarouter.provisioning.utils.RLEBitSet;\r
+import com.att.research.datarouter.provisioning.utils.LogfileLoader;\r
+\r
+/**\r
+ * <p>\r
+ * This servlet handles requests to URLs under /internal on the provisioning server.\r
+ * These include:\r
+ * </p>\r
+ * <div class="contentContainer">\r
+ * <table class="packageSummary" border="0" cellpadding="3" cellspacing="0">\r
+ * <caption><span>URL Path Summary</span><span class="tabEnd"> </span></caption>\r
+ * <tr>\r
+ * <th class="colFirst" width="15%">URL Path</th>\r
+ * <th class="colOne">Method</th>\r
+ * <th class="colLast">Purpose</th>\r
+ * </tr>\r
+ * <tr class="altColor">\r
+ * <td class="colFirst">/internal/prov</td>\r
+ * <td class="colOne">GET</td>\r
+ * <td class="colLast">used to GET a full JSON copy of the provisioning data.</td>\r
+ * </tr>\r
+ * <tr class="rowColor">\r
+ * <td class="colFirst">/internal/fetchProv</td>\r
+ * <td class="colOne">GET</td>\r
+ * <td class="colLast">used to signal to a standby POD that the provisioning data should be fetched from the active POD.</td>\r
+ * </tr>\r
+ * <tr class="altColor">\r
+ * <td class="colFirst" rowspan="2">/internal/logs</td>\r
+ * <td class="colOne">GET</td>\r
+ * <td class="colLast">used to GET an index of log files and individual logs for this provisioning server.</td>\r
+ * </tr>\r
+ * <tr class="altColor">\r
+ * <td class="colOne">POST</td>\r
+ * <td class="colLast">used to POST log files from the individual nodes to this provisioning server.</td>\r
+ * </tr>\r
+ * <tr class="rowColor">\r
+ * <td class="colFirst" rowspan="4">/internal/api</td>\r
+ * <td class="colOne">GET</td>\r
+ * <td class="colLast">used to GET an individual parameter value. The parameter name is specified by the path after /api/.</td>\r
+ * </tr>\r
+ * <tr class="rowColor">\r
+ * <td class="colOne">PUT</td>\r
+ * <td class="colLast">used to set an individual parameter value. The parameter name is specified by the path after /api/.</td>\r
+ * </tr>\r
+ * <tr class="rowColor">\r
+ * <td class="colOne">DELETE</td>\r
+ * <td class="colLast">used to remove an individual parameter value. The parameter name is specified by the path after /api/.</td>\r
+ * </tr>\r
+ * <tr class="rowColor">\r
+ * <td class="colOne">POST</td>\r
+ * <td class="colLast">used to create a new individual parameter value. The parameter name is specified by the path after /api/.</td>\r
+ * </tr>\r
+ * <tr class="altColor">\r
+ * <td class="colFirst">/internal/halt</td>\r
+ * <td class="colOne">GET</td>\r
+ * <td class="colLast">used to halt the server (must be accessed from 127.0.0.1).</td>\r
+ * </tr>\r
+ * <tr class="rowColor">\r
+ * <td class="colFirst" rowspan="2">/internal/drlogs</td>\r
+ * <td class="colOne">GET</td>\r
+ * <td class="colLast">used to get a list of DR log entries available for retrieval.\r
+ * Note: these are the actual data router log entries sent to the provisioning server\r
+ * by the nodes, not the provisioning server's internal logs (access via /internal/logs above).\r
+ * The range is returned as a list of record sequence numbers.</td>\r
+ * </tr>\r
+ * <tr class="rowColor">\r
+ * <td class="colOne">POST</td>\r
+ * <td class="colLast">used to retrieve specific log entries.\r
+ * The sequence numbers of the records to fetch are POST-ed; the records matching the sequence numbers are returned.</td>\r
+ * </tr>\r
+ * <tr class="altColor">\r
+ * <td class="colFirst">/internal/route/*</td>\r
+ * <td class="colOne">*</td>\r
+ * <td class="colLast">URLs under this path are handled via the {@link com.att.research.datarouter.provisioning.RouteServlet}</td>\r
+ * </tr>\r
+ * </table>\r
+ * </div>\r
+ * <p>\r
+ * Authorization to use these URLs is a little different than for other URLs on the provisioning server.\r
+ * For the most part, the IP address that the request comes from should be either:\r
+ * </p>\r
+ * <ol>\r
+ * <li>an IP address of a provisioning server, or</li>\r
+ * <li>the IP address of a node (to allow access to /internal/prov), or</li>\r
+ * <li>an IP address from the "<i>special subnet</i>" which is configured with\r
+ * the PROV_SPECIAL_SUBNET parameter.\r
+ * </ol>\r
+ * <p>\r
+ * In addition, requests to /internal/halt can ONLY come from localhost (127.0.0.1) on the HTTP port.\r
+ * </p>\r
+ * <p>\r
+ * All DELETE/GET/PUT/POST requests made to /internal/api on this servlet on the standby server are\r
+ * proxied to the active server (using the {@link ProxyServlet}) if it is up and reachable.\r
+ * </p>\r
+ *\r
+ * @author Robert Eby\r
+ * @version $Id: InternalServlet.java,v 1.23 2014/03/24 18:47:10 eby Exp $\r
+ */\r
+@SuppressWarnings("serial")\r
+public class InternalServlet extends ProxyServlet {\r
+ private static Integer logseq = new Integer(0); // another piece of info to make log spool file names unique\r
+ //Adding EELF Logger Rally:US664892 \r
+ private static EELFLogger eelflogger = EELFManager.getInstance().getLogger("com.att.research.datarouter.provisioning.InternalServlet");\r
+\r
+ /**\r
+ * Delete a parameter at the address /internal/api/<parameter>.\r
+ * See the <b>Internal API</b> document for details on how this method should be invoked.\r
+ */\r
+ @Override\r
+ public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
+ setIpAndFqdnForEelf("doDelete");\r
+ eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader(BEHALF_HEADER),getIdFromPath(req)+"");\r
+ EventLogRecord elr = new EventLogRecord(req);\r
+ if (!isAuthorizedForInternal(req)) {\r
+ elr.setMessage("Unauthorized.");\r
+ elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
+ eventlogger.info(elr);\r
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");\r
+ return;\r
+ }\r
+\r
+ String path = req.getPathInfo();\r
+ if (path.startsWith("/api/")) {\r
+ if (isProxyOK(req) && isProxyServer()) {\r
+ super.doDelete(req, resp);\r
+ return;\r
+ }\r
+ String key = path.substring(5);\r
+ if (key.length() > 0) {\r
+ Parameters param = Parameters.getParameter(key);\r
+ if (param != null) {\r
+ if (doDelete(param)) {\r
+ elr.setResult(HttpServletResponse.SC_OK);\r
+ eventlogger.info(elr);\r
+ resp.setStatus(HttpServletResponse.SC_OK);\r
+ provisioningDataChanged();\r
+ provisioningParametersChanged();\r
+ } else {\r
+ // Something went wrong with the DELETE\r
+ elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
+ eventlogger.info(elr);\r
+ resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);\r
+ }\r
+ return;\r
+ }\r
+ }\r
+ }\r
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");\r
+ }\r
+ /**\r
+ * Get some information (such as a parameter) underneath the /internal/ namespace.\r
+ * See the <b>Internal API</b> document for details on how this method should be invoked.\r
+ */\r
+ @Override\r
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
+ setIpAndFqdnForEelf("doGet");\r
+ eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader(BEHALF_HEADER),getIdFromPath(req)+"");\r
+ String path = req.getPathInfo();\r
+ if (path.equals("/halt") && !req.isSecure()) {\r
+ // request to halt the server - can ONLY come from localhost\r
+ String remote = req.getRemoteAddr();\r
+ if (remote.equals("127.0.0.1")) {\r
+ intlogger.info("PROV0009 Request to HALT received.");\r
+ resp.setStatus(HttpServletResponse.SC_OK);\r
+ Main.shutdown();\r
+ } else {\r
+ intlogger.info("PROV0010 Disallowed request to HALT received from "+remote);\r
+ resp.setStatus(HttpServletResponse.SC_FORBIDDEN);\r
+ }\r
+ return;\r
+ }\r
+\r
+ EventLogRecord elr = new EventLogRecord(req);\r
+ if (!isAuthorizedForInternal(req)) {\r
+ elr.setMessage("Unauthorized.");\r
+ elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
+ eventlogger.info(elr);\r
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");\r
+ return;\r
+ }\r
+ if (path.equals("/fetchProv") && !req.isSecure()) {\r
+ // if request came from active_pod or standby_pod and it is not us, reload prov data\r
+ SynchronizerTask s = SynchronizerTask.getSynchronizer();\r
+ s.doFetch();\r
+ resp.setStatus(HttpServletResponse.SC_OK);\r
+ return;\r
+ }\r
+ if (path.equals("/prov")) {\r
+ if (isProxyOK(req) && isProxyServer()) {\r
+ if (super.doGetWithFallback(req, resp))\r
+ return;\r
+ // fall back to returning the local data if the remote is unreachable\r
+ intlogger.info("Active server unavailable; falling back to local copy.");\r
+ }\r
+ Poker p = Poker.getPoker();\r
+ resp.setStatus(HttpServletResponse.SC_OK);\r
+ resp.setContentType(PROVFULL_CONTENT_TYPE2);\r
+ resp.getOutputStream().print(p.getProvisioningString());\r
+ return;\r
+ }\r
+ if (path.equals("/logs") || path.equals("/logs/")) {\r
+ resp.setStatus(HttpServletResponse.SC_OK);\r
+ resp.setContentType("application/json");\r
+ resp.getOutputStream().print(generateLogfileList().toString());\r
+ return;\r
+ }\r
+ if (path.startsWith("/logs/")) {\r
+ Properties p = (new DB()).getProperties();\r
+ String logdir = p.getProperty("com.att.research.datarouter.provserver.accesslog.dir");\r
+ String logfile = path.substring(6);\r
+ if (logdir != null && logfile != null && logfile.indexOf('/') < 0) {\r
+ File log = new File(logdir + "/" + logfile);\r
+ if (log.exists() && log.isFile()) {\r
+ resp.setStatus(HttpServletResponse.SC_OK);\r
+ resp.setContentType("text/plain");\r
+ Path logpath = Paths.get(log.getAbsolutePath());\r
+ Files.copy(logpath, resp.getOutputStream());\r
+ return;\r
+ }\r
+ }\r
+ resp.sendError(HttpServletResponse.SC_NO_CONTENT, "No file.");\r
+ return;\r
+ }\r
+ if (path.startsWith("/api/")) {\r
+ if (isProxyOK(req) && isProxyServer()) {\r
+ super.doGet(req, resp);\r
+ return;\r
+ }\r
+ String key = path.substring(5);\r
+ if (key.length() > 0) {\r
+ Parameters param = Parameters.getParameter(key);\r
+ if (param != null) {\r
+ resp.setStatus(HttpServletResponse.SC_OK);\r
+ resp.setContentType("text/plain");\r
+ resp.getOutputStream().print(param.getValue() + "\n");\r
+ return;\r
+ }\r
+ }\r
+ }\r
+ if (path.equals("/drlogs") || path.equals("/drlogs/")) {\r
+ // Special POD <=> POD API to determine what log file records are loaded here\r
+ LogfileLoader lfl = LogfileLoader.getLoader();\r
+ resp.setStatus(HttpServletResponse.SC_OK);\r
+ resp.setContentType("text/plain");\r
+ resp.getOutputStream().print(lfl.getBitSet().toString());\r
+ return;\r
+ }\r
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");\r
+ }\r
+ /**\r
+ * Modify a parameter at the address /internal/api/<parameter>.\r
+ * See the <b>Internal API</b> document for details on how this method should be invoked.\r
+ */\r
+ @Override\r
+ public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
+ setIpAndFqdnForEelf("doPut");\r
+ eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader(BEHALF_HEADER),getIdFromPath(req)+"");\r
+ EventLogRecord elr = new EventLogRecord(req);\r
+ if (!isAuthorizedForInternal(req)) {\r
+ elr.setMessage("Unauthorized.");\r
+ elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
+ eventlogger.info(elr);\r
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");\r
+ return;\r
+ }\r
+ String path = req.getPathInfo();\r
+ if (path.startsWith("/api/")) {\r
+ if (isProxyOK(req) && isProxyServer()) {\r
+ super.doPut(req, resp);\r
+ return;\r
+ }\r
+ String key = path.substring(5);\r
+ if (key.length() > 0) {\r
+ Parameters param = Parameters.getParameter(key);\r
+ if (param != null) {\r
+ String t = catValues(req.getParameterValues("val"));\r
+ param.setValue(t);\r
+ if (doUpdate(param)) {\r
+ elr.setResult(HttpServletResponse.SC_OK);\r
+ eventlogger.info(elr);\r
+ resp.setStatus(HttpServletResponse.SC_OK);\r
+ provisioningDataChanged();\r
+ provisioningParametersChanged();\r
+ } else {\r
+ // Something went wrong with the UPDATE\r
+ elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
+ eventlogger.info(elr);\r
+ resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);\r
+ }\r
+ return;\r
+ }\r
+ }\r
+ }\r
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");\r
+ }\r
+ /**\r
+ * Create some new information (such as a parameter or log entries) underneath the /internal/ namespace.\r
+ * See the <b>Internal API</b> document for details on how this method should be invoked.\r
+ */\r
+ @SuppressWarnings("resource")\r
+ @Override\r
+ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
+ setIpAndFqdnForEelf("doPost");\r
+ eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF, req.getHeader(BEHALF_HEADER));\r
+ EventLogRecord elr = new EventLogRecord(req);\r
+ if (!isAuthorizedForInternal(req)) {\r
+ elr.setMessage("Unauthorized.");\r
+ elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
+ eventlogger.info(elr);\r
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");\r
+ return;\r
+ }\r
+\r
+ String path = req.getPathInfo();\r
+ if (path.startsWith("/api/")) {\r
+ if (isProxyOK(req) && isProxyServer()) {\r
+ super.doPost(req, resp);\r
+ return;\r
+ }\r
+ String key = path.substring(5);\r
+ if (key.length() > 0) {\r
+ Parameters param = Parameters.getParameter(key);\r
+ if (param == null) {\r
+ String t = catValues(req.getParameterValues("val"));\r
+ param = new Parameters(key, t);\r
+ if (doInsert(param)) {\r
+ elr.setResult(HttpServletResponse.SC_OK);\r
+ eventlogger.info(elr);\r
+ resp.setStatus(HttpServletResponse.SC_OK);\r
+ provisioningDataChanged();\r
+ provisioningParametersChanged();\r
+ } else {\r
+ // Something went wrong with the INSERT\r
+ elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
+ eventlogger.info(elr);\r
+ resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);\r
+ }\r
+ return;\r
+ }\r
+ }\r
+ }\r
+\r
+ if (path.equals("/logs") || path.equals("/logs/")) {\r
+ String ctype = req.getHeader("Content-Type");\r
+ if (ctype == null || !ctype.equals("text/plain")) {\r
+ elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);\r
+ elr.setMessage("Bad media type: "+ctype);\r
+ resp.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);\r
+ eventlogger.info(elr);\r
+ return;\r
+ }\r
+ String spooldir = (new DB()).getProperties().getProperty("com.att.research.datarouter.provserver.spooldir");\r
+ String spoolname = String.format("%d-%d-", System.currentTimeMillis(), Thread.currentThread().getId());\r
+ synchronized (logseq) {\r
+ // perhaps unnecessary, but it helps make the name unique\r
+ spoolname += logseq.toString();\r
+ logseq++;\r
+ }\r
+ String encoding = req.getHeader("Content-Encoding");\r
+ if (encoding != null) {\r
+ if (encoding.trim().equals("gzip")) {\r
+ spoolname += ".gz";\r
+ } else {\r
+ elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);\r
+ resp.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);\r
+ eventlogger.info(elr);\r
+ return;\r
+ }\r
+ }\r
+ // Determine space available -- available space must be at least 5%\r
+ FileSystem fs = (Paths.get(spooldir)).getFileSystem();\r
+ long total = 0;\r
+ long avail = 0;\r
+ for (FileStore store: fs.getFileStores()) {\r
+ total += store.getTotalSpace();\r
+ avail += store.getUsableSpace();\r
+ }\r
+ try { fs.close(); } catch (Exception e) { }\r
+ if (((avail * 100) / total) < 5) {\r
+ elr.setResult(HttpServletResponse.SC_SERVICE_UNAVAILABLE);\r
+ resp.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);\r
+ eventlogger.info(elr);\r
+ return;\r
+ }\r
+ Path tmppath = Paths.get(spooldir, spoolname);\r
+ Path donepath = Paths.get(spooldir, "IN."+spoolname);\r
+ Files.copy(req.getInputStream(), Paths.get(spooldir, spoolname), StandardCopyOption.REPLACE_EXISTING);\r
+ Files.move(tmppath, donepath, StandardCopyOption.REPLACE_EXISTING);\r
+ elr.setResult(HttpServletResponse.SC_CREATED);\r
+ resp.setStatus(HttpServletResponse.SC_CREATED);\r
+ eventlogger.info(elr);\r
+ LogfileLoader.getLoader(); // This starts the logfile loader "task"\r
+ return;\r
+ }\r
+\r
+ if (path.equals("/drlogs") || path.equals("/drlogs/")) {\r
+ // Receive post request and generate log entries\r
+ String ctype = req.getHeader("Content-Type");\r
+ if (ctype == null || !ctype.equals("text/plain")) {\r
+ elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);\r
+ elr.setMessage("Bad media type: "+ctype);\r
+ resp.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);\r
+ eventlogger.info(elr);\r
+ return;\r
+ }\r
+ InputStream is = req.getInputStream();\r
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();\r
+ int ch = 0;\r
+ while ((ch = is.read()) >= 0)\r
+ bos.write(ch);\r
+ RLEBitSet bs = new RLEBitSet(bos.toString()); // The set of records to retrieve\r
+ elr.setResult(HttpServletResponse.SC_OK);\r
+ resp.setStatus(HttpServletResponse.SC_OK);\r
+ resp.setContentType("text/plain");\r
+ LogRecord.printLogRecords(resp.getOutputStream(), bs);\r
+ eventlogger.info(elr);\r
+ return;\r
+ }\r
+\r
+ elr.setResult(HttpServletResponse.SC_NOT_FOUND);\r
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");\r
+ eventlogger.info(elr);\r
+ }\r
+\r
+ private String catValues(String[] v) {\r
+ StringBuilder sb = new StringBuilder();\r
+ if (v != null) {\r
+ String pfx = "";\r
+ for (String s : v) {\r
+ sb.append(pfx);\r
+ sb.append(s);\r
+ pfx = "|";\r
+ }\r
+ }\r
+ return sb.toString();\r
+ }\r
+ private JSONArray generateLogfileList() {\r
+ JSONArray ja = new JSONArray();\r
+ Properties p = (new DB()).getProperties();\r
+ String s = p.getProperty("com.att.research.datarouter.provserver.accesslog.dir");\r
+ if (s != null) {\r
+ String[] dirs = s.split(",");\r
+ for (String dir : dirs) {\r
+ File f = new File(dir);\r
+ String[] list = f.list();\r
+ if (list != null) {\r
+ for (String s2 : list) {\r
+ if (!s2.startsWith("."))\r
+ ja.put(s2);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ return ja;\r
+ }\r
+}\r