[DMAAP-48] Initial code import
[dmaap/datarouter.git] / datarouter-prov / src / main / java / com / att / research / datarouter / provisioning / RouteServlet.java
1 /*******************************************************************************\r
2  * ============LICENSE_START==================================================\r
3  * * org.onap.dmaap\r
4  * * ===========================================================================\r
5  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.\r
6  * * ===========================================================================\r
7  * * Licensed under the Apache License, Version 2.0 (the "License");\r
8  * * you may not use this file except in compliance with the License.\r
9  * * You may obtain a copy of the License at\r
10  * * \r
11  *  *      http://www.apache.org/licenses/LICENSE-2.0\r
12  * * \r
13  *  * Unless required by applicable law or agreed to in writing, software\r
14  * * distributed under the License is distributed on an "AS IS" BASIS,\r
15  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
16  * * See the License for the specific language governing permissions and\r
17  * * limitations under the License.\r
18  * * ============LICENSE_END====================================================\r
19  * *\r
20  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
21  * *\r
22  ******************************************************************************/\r
23 \r
24 \r
25 package com.att.research.datarouter.provisioning;\r
26 \r
27 import java.io.IOException;\r
28 import java.util.Set;\r
29 \r
30 import javax.servlet.http.HttpServletRequest;\r
31 import javax.servlet.http.HttpServletResponse;\r
32 \r
33 import org.json.JSONObject;\r
34 \r
35 import com.att.research.datarouter.provisioning.beans.Deleteable;\r
36 import com.att.research.datarouter.provisioning.beans.EgressRoute;\r
37 import com.att.research.datarouter.provisioning.beans.EventLogRecord;\r
38 import com.att.research.datarouter.provisioning.beans.IngressRoute;\r
39 import com.att.research.datarouter.provisioning.beans.Insertable;\r
40 import com.att.research.datarouter.provisioning.beans.NetworkRoute;\r
41 import com.att.research.datarouter.provisioning.beans.NodeClass;\r
42 \r
43 /**\r
44  * <p>\r
45  * This servlet handles requests to URLs under /internal/route/ on the provisioning server.\r
46  * This part of the URL tree is used to manipulate the Data Router routing tables.\r
47  * These include:\r
48  * </p>\r
49  * <div class="contentContainer">\r
50  * <table class="packageSummary" border="0" cellpadding="3" cellspacing="0">\r
51  * <caption><span>URL Path Summary</span><span class="tabEnd">&nbsp;</span></caption>\r
52  * <tr>\r
53  *   <th class="colFirst" width="35%">URL Path</th>\r
54  *   <th class="colOne">Method</th>\r
55  *   <th class="colLast">Purpose</th>\r
56  * </tr>\r
57  * <tr class="altColor">\r
58  *   <td class="colFirst">/internal/route/</td>\r
59  *   <td class="colOne">GET</td>\r
60  *   <td class="colLast">used to GET a full JSON copy of all three routing tables.</td>\r
61  * </tr>\r
62  * <tr class="rowColor">\r
63  *   <td class="colFirst" rowspan="2">/internal/route/ingress/</td>\r
64  *   <td class="colOne">GET</td>\r
65  *   <td class="colLast">used to GET a full JSON copy of the ingress routing table (IRT).</td>\r
66  * </tr>\r
67  * <tr class="rowColor">\r
68  *   <td class="colOne">POST</td>\r
69  *   <td class="colLast">used to create a new entry in the ingress routing table (IRT).</td></tr>\r
70  * <tr class="altColor">\r
71  *   <td class="colFirst" rowspan="2">/internal/route/egress/</td>\r
72  *   <td class="colOne">GET</td>\r
73  *   <td class="colLast">used to GET a full JSON copy of the egress routing table (ERT).</td>\r
74  * </tr>\r
75  * <tr class="altColor">\r
76  *   <td class="colOne">POST</td>\r
77  *   <td class="colLast">used to create a new entry in the egress routing table (ERT).</td></tr>\r
78  * <tr class="rowColor">\r
79  *   <td class="colFirst" rowspan="2">/internal/route/network/</td>\r
80  *   <td class="colOne">GET</td>\r
81  *   <td class="colLast">used to GET a full JSON copy of the network routing table (NRT).</td>\r
82  * </tr>\r
83  * <tr class="rowColor">\r
84  *   <td class="colOne">POST</td>\r
85  *   <td class="colLast">used to create a new entry in the network routing table (NRT).</td>\r
86  * </tr>\r
87  * <tr class="altColor">\r
88  *   <td class="colFirst">/internal/route/ingress/&lt;feed&gt;/&lt;user&gt;/&lt;subnet&gt;</td>\r
89  *   <td class="colOne">DELETE</td>\r
90  *   <td class="colLast">used to DELETE the ingress route corresponding to <i>feed</i>, <i>user</i> and <i>subnet</i>.\r
91  *   The / in the subnet specified should be replaced with a !, since / cannot be used in a URL.</td>\r
92  * </tr>\r
93  * <tr class="rowColor">\r
94  *   <td class="colFirst">/internal/route/ingress/&lt;seq&gt;</td>\r
95  *   <td class="colOne">DELETE</td>\r
96  *   <td class="colLast">used to DELETE all ingress routes with the matching <i>seq</i> sequence number.</td>\r
97  * </tr>\r
98  * <tr class="altColor">\r
99  *   <td class="colFirst">/internal/route/egress/&lt;sub&gt;</td>\r
100  *   <td class="colOne">DELETE</td>\r
101  *   <td class="colLast">used to DELETE the egress route the matching <i>sub</i> subscriber number.</td>\r
102  * </tr>\r
103  * <tr class="rowColor">\r
104  *   <td class="colFirst">/internal/route/network/&lt;fromnode&gt;/&lt;tonode&gt;</td>\r
105  *   <td class="colOne">DELETE</td>\r
106  *   <td class="colLast">used to DELETE the network route corresponding to <i>fromnode</i>\r
107  *   and <i>tonode</i>.</td>\r
108  * </tr>\r
109  * </table>\r
110  * <p>\r
111  * Authorization to use these URLs is a little different than for other URLs on the provisioning server.\r
112  * For the most part, the IP address that the request comes from should be either:\r
113  * </p>\r
114  * <ol>\r
115  * <li>an IP address of a provisioning server, or</li>\r
116  * <li>the IP address of a node, or</li>\r
117  * <li>an IP address from the "<i>special subnet</i>" which is configured with\r
118  * the PROV_SPECIAL_SUBNET parameter.\r
119  * </ol>\r
120  * <p>\r
121  * All DELETE/GET/POST requests made to this servlet on the standby server are proxied to the\r
122  * active server (using the {@link ProxyServlet}) if it is up and reachable.\r
123  * </p>\r
124  *\r
125  * @author Robert Eby\r
126  * @version $Id$\r
127  */\r
128 @SuppressWarnings("serial")\r
129 public class RouteServlet extends ProxyServlet {\r
130         /**\r
131          * DELETE route table entries by deleting part of the route table tree.\r
132          */\r
133         @Override\r
134         public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
135                 EventLogRecord elr = new EventLogRecord(req);\r
136                 if (!isAuthorizedForInternal(req)) {\r
137                         elr.setMessage("Unauthorized.");\r
138                         elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
139                         eventlogger.info(elr);\r
140                         resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");\r
141                         return;\r
142                 }\r
143                 if (isProxyOK(req) && isProxyServer()) {\r
144                         super.doDelete(req, resp);\r
145                         return;\r
146                 }\r
147 \r
148                 String path = req.getPathInfo();\r
149                 String[] parts = path.substring(1).split("/");\r
150                 Deleteable[] d = null;\r
151                 if (parts[0].equals("ingress")) {\r
152                         if (parts.length == 4) {\r
153                                 // /internal/route/ingress/<feed>/<user>/<subnet>\r
154                                 try {\r
155                                         int feedid = Integer.parseInt(parts[1]);\r
156                                         IngressRoute er = IngressRoute.getIngressRoute(feedid, parts[2], parts[3].replaceAll("!", "/"));\r
157                                         if (er == null) {\r
158                                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "The specified ingress route does not exist.");\r
159                                                 return;\r
160                                         }\r
161                                         d = new Deleteable[] { er };\r
162                                 } catch (NumberFormatException e) {\r
163                                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid feed ID in 'delete ingress' command.");\r
164                                         return;\r
165                                 }\r
166                         } else if (parts.length == 2) {\r
167                                 // /internal/route/ingress/<seq>\r
168                                 try {\r
169                                         int seq = Integer.parseInt(parts[1]);\r
170                                         Set<IngressRoute> set = IngressRoute.getIngressRoutesForSeq(seq);\r
171                                         d = set.toArray(new Deleteable[0]);\r
172                                 } catch (NumberFormatException e) {\r
173                                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid sequence number in 'delete ingress' command.");\r
174                                         return;\r
175                                 }\r
176                         } else {\r
177                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid number of arguments in 'delete ingress' command.");\r
178                                 return;\r
179                         }\r
180                 } else if (parts[0].equals("egress")) {\r
181                         if (parts.length == 2) {\r
182                                 // /internal/route/egress/<sub>\r
183                                 try {\r
184                                         int subid = Integer.parseInt(parts[1]);\r
185                                         EgressRoute er = EgressRoute.getEgressRoute(subid);\r
186                                         if (er == null) {\r
187                                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "The specified egress route does not exist.");\r
188                                                 return;\r
189                                         }\r
190                                         d = new Deleteable[] { er };\r
191                                 } catch (NumberFormatException e) {\r
192                                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid sub ID in 'delete egress' command.");\r
193                                         return;\r
194                                 }\r
195                         } else {\r
196                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid number of arguments in 'delete egress' command.");\r
197                                 return;\r
198                         }\r
199                 } else if (parts[0].equals("network")) {\r
200                         if (parts.length == 3) {\r
201                                 // /internal/route/network/<from>/<to>\r
202                                 try {//\r
203                                         NetworkRoute nr = new NetworkRoute(\r
204                                                 NodeClass.normalizeNodename(parts[1]),\r
205                                                 NodeClass.normalizeNodename(parts[2])\r
206                                         );\r
207                                         d = new Deleteable[] { nr };\r
208                                 } catch (IllegalArgumentException e) {\r
209                                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "The specified network route does not exist.");\r
210                                         return;\r
211                                 }\r
212                         } else {\r
213                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid number of arguments in 'delete network' command.");\r
214                                 return;\r
215                         }\r
216                 }\r
217                 if (d == null) {\r
218                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");\r
219                         return;\r
220                 }\r
221                 boolean rv = true;\r
222                 for (Deleteable dd : d) {\r
223                         rv &= doDelete(dd);\r
224                 }\r
225                 if (rv) {\r
226                         elr.setResult(HttpServletResponse.SC_OK);\r
227                         eventlogger.info(elr);\r
228                         resp.setStatus(HttpServletResponse.SC_OK);\r
229                         provisioningDataChanged();\r
230                         provisioningParametersChanged();\r
231                 } else {\r
232                         // Something went wrong with the DELETE\r
233                         elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
234                         eventlogger.info(elr);\r
235                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);\r
236                 }\r
237         }\r
238         /**\r
239          * GET route table entries from the route table tree specified by the URL path.\r
240          */\r
241         @Override\r
242         public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
243                 EventLogRecord elr = new EventLogRecord(req);\r
244                 if (!isAuthorizedForInternal(req)) {\r
245                         elr.setMessage("Unauthorized.");\r
246                         elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
247                         eventlogger.info(elr);\r
248                         resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");\r
249                         return;\r
250                 }\r
251                 if (isProxyOK(req) && isProxyServer()) {\r
252                         super.doGet(req, resp);\r
253                         return;\r
254                 }\r
255 \r
256                 String path = req.getPathInfo();\r
257                 if (!path.endsWith("/"))\r
258                         path += "/";\r
259                 if (!path.equals("/") && !path.equals("/ingress/") && !path.equals("/egress/") && !path.equals("/network/")) {\r
260                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");\r
261                         return;\r
262                 }\r
263 \r
264                 StringBuilder sb = new StringBuilder("{\n");\r
265                 String px2 = "";\r
266                 if (path.equals("/") || path.equals("/ingress/")) {\r
267                         String pfx = "\n";\r
268                         sb.append("\"ingress\": [");\r
269                         for (IngressRoute in : IngressRoute.getAllIngressRoutes()) {\r
270                                 sb.append(pfx);\r
271                                 sb.append(in.asJSONObject().toString());\r
272                                 pfx = ",\n";\r
273                         }\r
274                         sb.append("\n]");\r
275                         px2 = ",\n";\r
276                 }\r
277 \r
278                 if (path.equals("/") || path.equals("/egress/")) {\r
279                         String pfx = "\n";\r
280                         sb.append(px2);\r
281                         sb.append("\"egress\": {");\r
282                         for (EgressRoute eg : EgressRoute.getAllEgressRoutes()) {\r
283                                 JSONObject jx = eg.asJSONObject();\r
284                                 for (String key : jx.keySet()) {\r
285                                         sb.append(pfx);\r
286                                         sb.append("  \"").append(key).append("\": ");\r
287                                         sb.append("\"").append(jx.getString(key)).append("\"");\r
288                                         pfx = ",\n";\r
289                                 }\r
290                         }\r
291                         sb.append("\n}");\r
292                         px2 = ",\n";\r
293                 }\r
294 \r
295                 if (path.equals("/") || path.equals("/network/")) {\r
296                         String pfx = "\n";\r
297                         sb.append(px2);\r
298                         sb.append("\"routing\": [");\r
299                         for (NetworkRoute ne : NetworkRoute.getAllNetworkRoutes()) {\r
300                                 sb.append(pfx);\r
301                                 sb.append(ne.asJSONObject().toString());\r
302                                 pfx = ",\n";\r
303                         }\r
304                         sb.append("\n]");\r
305                 }\r
306                 sb.append("}\n");\r
307                 resp.setStatus(HttpServletResponse.SC_OK);\r
308                 resp.setContentType("application/json");\r
309                 resp.getOutputStream().print(sb.toString());\r
310         }\r
311         /**\r
312          * PUT on &lt;/internal/route/*&gt; -- not supported.\r
313          */\r
314         @Override\r
315         public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
316                 EventLogRecord elr = new EventLogRecord(req);\r
317                 if (!isAuthorizedForInternal(req)) {\r
318                         elr.setMessage("Unauthorized.");\r
319                         elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
320                         eventlogger.info(elr);\r
321                         resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");\r
322                         return;\r
323                 }\r
324                 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");\r
325         }\r
326         /**\r
327          * POST - modify existing route table entries in the route table tree specified by the URL path.\r
328          */\r
329         @Override\r
330         public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
331                 EventLogRecord elr = new EventLogRecord(req);\r
332                 if (!isAuthorizedForInternal(req)) {\r
333                         elr.setMessage("Unauthorized.");\r
334                         elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
335                         eventlogger.info(elr);\r
336                         resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");\r
337                         return;\r
338                 }\r
339                 if (isProxyOK(req) && isProxyServer()) {\r
340                         super.doPost(req, resp);\r
341                         return;\r
342                 }\r
343                 String path = req.getPathInfo();\r
344                 Insertable[] ins = null;\r
345                 if (path.startsWith("/ingress/")) {\r
346                         // /internal/route/ingress/?feed=%s&amp;user=%s&amp;subnet=%s&amp;nodepatt=%s\r
347                         try {\r
348                                 // Although it probably doesn't make sense, you can install two identical routes in the IRT\r
349                                 int feedid = Integer.parseInt(req.getParameter("feed"));\r
350                                 String user = req.getParameter("user");\r
351                                 if (user == null)\r
352                                         user = "-";\r
353                                 String subnet = req.getParameter("subnet");\r
354                                 if (subnet == null)\r
355                                         subnet = "-";\r
356                                 String nodepatt = req.getParameter("nodepatt");\r
357                                 String t = req.getParameter("seq");\r
358                                 int seq = (t != null) ? Integer.parseInt(t) : (IngressRoute.getMaxSequence() + 100);\r
359                                 ins = new Insertable[] { new IngressRoute(seq, feedid, user, subnet, NodeClass.lookupNodeNames(nodepatt)) };\r
360                         } catch (Exception e) {\r
361                                 intlogger.info(e);\r
362                                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid arguments in 'add ingress' command.");\r
363                                 return;\r
364                         }\r
365                 } else if (path.startsWith("/egress/")) {\r
366                         // /internal/route/egress/?sub=%s&amp;node=%s\r
367                         try {\r
368                                 int subid = Integer.parseInt(req.getParameter("sub"));\r
369                                 EgressRoute er = EgressRoute.getEgressRoute(subid);\r
370                                 if (er != null) {\r
371                                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "An egress route already exists for that subscriber.");\r
372                                         return;\r
373                                 }\r
374                                 String node = NodeClass.normalizeNodename(req.getParameter("node"));\r
375                                 ins = new Insertable[] { new EgressRoute(subid, node) };\r
376                         } catch (Exception e) {\r
377                                 intlogger.info(e);\r
378                                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid arguments in 'add egress' command.");\r
379                                 return;\r
380                         }\r
381                 } else if (path.startsWith("/network/")) {\r
382                         // /internal/route/network/?from=%s&amp;to=%s&amp;via=%s\r
383                         try {\r
384                                 String nfrom = req.getParameter("from");\r
385                                 String nto   = req.getParameter("to");\r
386                                 String nvia  = req.getParameter("via");\r
387                                 if (nfrom == null || nto == null || nvia == null) {\r
388                                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing arguments in 'add network' command.");\r
389                                         return;\r
390                                 }\r
391                                 nfrom = NodeClass.normalizeNodename(nfrom);\r
392                                 nto   = NodeClass.normalizeNodename(nto);\r
393                                 nvia  = NodeClass.normalizeNodename(nvia);\r
394                                 NetworkRoute nr = new NetworkRoute(nfrom, nto, nvia);\r
395                                 for (NetworkRoute route : NetworkRoute.getAllNetworkRoutes()) {\r
396                                         if (route.getFromnode() == nr.getFromnode() && route.getTonode() == nr.getTonode()) {\r
397                                                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Network route table already contains a route for "+nfrom+" and "+nto);\r
398                                                 return;\r
399                                         }\r
400                                 }\r
401                                 ins = new Insertable[] { nr };\r
402                         } catch (IllegalArgumentException e) {\r
403                                 intlogger.info(e);\r
404                                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid arguments in 'add network' command.");\r
405                                 return;\r
406                         }\r
407                 }\r
408                 if (ins == null) {\r
409                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");\r
410                         return;\r
411                 }\r
412                 boolean rv = true;\r
413                 for (Insertable dd : ins) {\r
414                         rv &= doInsert(dd);\r
415                 }\r
416                 if (rv) {\r
417                         elr.setResult(HttpServletResponse.SC_OK);\r
418                         eventlogger.info(elr);\r
419                         resp.setStatus(HttpServletResponse.SC_OK);\r
420                         provisioningDataChanged();\r
421                         provisioningParametersChanged();\r
422                 } else {\r
423                         // Something went wrong with the INSERT\r
424                         elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
425                         eventlogger.info(elr);\r
426                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);\r
427                 }\r
428         }\r
429 }\r