/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.solr.servlet; import java.io.IOException; import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.URLEncoder; import java.util.Date; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.IOUtils; import org.noggit.CharArr; import org.noggit.JSONWriter; import org.apache.solr.cloud.ZkController; import org.apache.solr.common.SolrException; import org.apache.solr.common.cloud.SolrZkClient; import org.apache.solr.common.params.SolrParams; import org.apache.solr.core.CoreContainer; import org.apache.solr.util.FastWriter; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.data.Stat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Zookeeper Info * * @since solr 4.0 */ public final class ZookeeperInfoServlet extends HttpServlet { static final Logger log = LoggerFactory.getLogger(ZookeeperInfoServlet.class); @Override public void init() { } @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException { // This attribute is set by the SolrDispatchFilter CoreContainer cores = (CoreContainer) request.getAttribute("org.apache.solr.CoreContainer"); if (cores == null) { throw new ServletException("Missing request attribute org.apache.solr.CoreContainer."); } final SolrParams params; try { params = SolrRequestParsers.DEFAULT.parse(null, request.getServletPath(), request).getParams(); } catch (Exception e) { int code=500; if (e instanceof SolrException) { code = Math.min(599, Math.max(100, ((SolrException)e).code())); } response.sendError(code, e.toString()); return; } String path = params.get("path"); String addr = params.get("addr"); if (addr != null && addr.length() == 0) { addr = null; } String detailS = params.get("detail"); boolean detail = detailS != null && detailS.equals("true"); String dumpS = params.get("dump"); boolean dump = dumpS != null && dumpS.equals("true"); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); Writer out = new FastWriter(new OutputStreamWriter(response.getOutputStream(), IOUtils.CHARSET_UTF_8)); ZKPrinter printer = new ZKPrinter(response, out, cores.getZkController(), addr); printer.detail = detail; printer.dump = dump; try { printer.print(path); } finally { printer.close(); } out.flush(); } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException { doGet(request, response); } //-------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------- static class ZKPrinter { static boolean FULLPATH_DEFAULT = false; boolean indent = true; boolean fullpath = FULLPATH_DEFAULT; boolean detail = false; boolean dump = false; String addr; // the address passed to us String keeperAddr; // the address we're connected to boolean doClose; // close the client after done if we opened it final HttpServletResponse response; final Writer out; SolrZkClient zkClient; int level; int maxData = 95; public ZKPrinter(HttpServletResponse response, Writer out, ZkController controller, String addr) throws IOException { this.response = response; this.out = out; this.addr = addr; if (addr == null) { if (controller != null) { // this core is zk enabled keeperAddr = controller.getZkServerAddress(); zkClient = controller.getZkClient(); if (zkClient != null && zkClient.isConnected()) { return; } else { // try a different client with this address addr = keeperAddr; } } } keeperAddr = addr; if (addr == null) { writeError(404, "Zookeeper is not configured for this Solr Core. Please try connecting to an alternate zookeeper address."); return; } try { zkClient = new SolrZkClient(addr, 10000); doClose = true; } catch (Exception e) { writeError(503, "Could not connect to zookeeper at '" + addr + "'\""); zkClient = null; return; } } public void close() { if (doClose) { zkClient.close(); } } // main entry point void print(String path) throws IOException { if (zkClient == null) { return; } // normalize path if (path == null) { path = "/"; } else { path = path.trim(); if (path.length() == 0) { path = "/"; } } if (path.endsWith("/") && path.length() > 1) { path = path.substring(0, path.length() - 1); } int idx = path.lastIndexOf('/'); String parent = idx >= 0 ? path.substring(0, idx) : path; if (parent.length() == 0) { parent = "/"; } CharArr chars = new CharArr(); JSONWriter json = new JSONWriter(chars, 2); json.startObject(); if (detail) { if (!printZnode(json, path)) { return; } json.writeValueSeparator(); } json.writeString("tree"); json.writeNameSeparator(); json.startArray(); if (!printTree(json, path)) { return; // there was an error } json.endArray(); json.endObject(); out.write(chars.toString()); } void writeError(int code, String msg) throws IOException { response.setStatus(code); CharArr chars = new CharArr(); JSONWriter w = new JSONWriter(chars, 2); w.startObject(); w.indent(); w.writeString("status"); w.writeNameSeparator(); w.write(code); w.writeValueSeparator(); w.indent(); w.writeString("error"); w.writeNameSeparator(); w.writeString(msg); w.endObject(); out.write(chars.toString()); } boolean printTree(JSONWriter json, String path) throws IOException { String label = path; if (!fullpath) { int idx = path.lastIndexOf('/'); label = idx > 0 ? path.substring(idx + 1) : path; } json.startObject(); //writeKeyValue(json, "data", label, true ); json.writeString("data"); json.writeNameSeparator(); json.startObject(); writeKeyValue(json, "title", label, true); json.writeValueSeparator(); json.writeString("attr"); json.writeNameSeparator(); json.startObject(); writeKeyValue(json, "href", "zookeeper?detail=true&path=" + URLEncoder.encode(path, "UTF-8"), true); json.endObject(); json.endObject(); Stat stat = new Stat(); try { // Trickily, the call to zkClient.getData fills in the stat variable byte[] data = zkClient.getData(path, null, stat, true); if (stat.getEphemeralOwner() != 0) { writeKeyValue(json, "ephemeral", true, false); writeKeyValue(json, "version", stat.getVersion(), false); } if (dump) { json.writeValueSeparator(); printZnode(json, path); } } catch (IllegalArgumentException e) { // path doesn't exist (must have been removed) writeKeyValue(json, "warning", "(path gone)", false); } catch (KeeperException e) { writeKeyValue(json, "warning", e.toString(), false); log.warn("Keeper Exception", e); } catch (InterruptedException e) { writeKeyValue(json, "warning", e.toString(), false); log.warn("InterruptedException", e); } if (stat.getNumChildren() > 0) { json.writeValueSeparator(); if (indent) { json.indent(); } json.writeString("children"); json.writeNameSeparator(); json.startArray(); try { List<String> children = zkClient.getChildren(path, null, true); java.util.Collections.sort(children); boolean first = true; for (String child : children) { if (!first) { json.writeValueSeparator(); } String childPath = path + (path.endsWith("/") ? "" : "/") + child; if (!printTree(json, childPath)) { return false; } first = false; } } catch (KeeperException e) { writeError(500, e.toString()); return false; } catch (InterruptedException e) { writeError(500, e.toString()); return false; } catch (IllegalArgumentException e) { // path doesn't exist (must have been removed) json.writeString("(children gone)"); } json.endArray(); } json.endObject(); return true; } String time(long ms) { return (new Date(ms)).toString() + " (" + ms + ")"; } public void writeKeyValue(JSONWriter json, String k, Object v, boolean isFirst) { if (!isFirst) { json.writeValueSeparator(); } if (indent) { json.indent(); } json.writeString(k); json.writeNameSeparator(); json.write(v); } boolean printZnode(JSONWriter json, String path) throws IOException { try { Stat stat = new Stat(); // Trickily, the call to zkClient.getData fills in the stat variable byte[] data = zkClient.getData(path, null, stat, true); String dataStr = null; String dataStrErr = null; if (null != data) { try { dataStr = (new BytesRef(data)).utf8ToString(); } catch (Exception e) { dataStrErr = "data is not parsable as a utf8 String: " + e.toString(); } } json.writeString("znode"); json.writeNameSeparator(); json.startObject(); writeKeyValue(json, "path", path, true); json.writeValueSeparator(); json.writeString("prop"); json.writeNameSeparator(); json.startObject(); writeKeyValue(json, "version", stat.getVersion(), true); writeKeyValue(json, "aversion", stat.getAversion(), false); writeKeyValue(json, "children_count", stat.getNumChildren(), false); writeKeyValue(json, "ctime", time(stat.getCtime()), false); writeKeyValue(json, "cversion", stat.getCversion(), false); writeKeyValue(json, "czxid", stat.getCzxid(), false); writeKeyValue(json, "ephemeralOwner", stat.getEphemeralOwner(), false); writeKeyValue(json, "mtime", time(stat.getMtime()), false); writeKeyValue(json, "mzxid", stat.getMzxid(), false); writeKeyValue(json, "pzxid", stat.getPzxid(), false); writeKeyValue(json, "dataLength", stat.getDataLength(), false); if (null != dataStrErr) { writeKeyValue(json, "dataNote", dataStrErr, false); } json.endObject(); if (null != dataStr) { writeKeyValue(json, "data", dataStr, false); } json.endObject(); } catch (KeeperException e) { writeError(500, e.toString()); return false; } catch (InterruptedException e) { writeError(500, e.toString()); return false; } return true; } } }