blob: 18fe4c37367ff0774f8b6ca3b9d4e50281b0f5db [file] [log] [blame]
/*
* 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.client.solrj;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.invoke.MethodHandles;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.io.IOUtils;
import org.apache.solr.SolrJettyTestBase;
import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
import org.apache.solr.client.solrj.impl.BinaryRequestWriter;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.RequestWriter;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrInputDocument;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressSSL(bugUrl = "https://issues.apache.org/jira/browse/SOLR-5776")
public class TestSolrJErrorHandling extends SolrJettyTestBase {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
List<Throwable> unexpected = new CopyOnWriteArrayList<>();
@BeforeClass
public static void beforeTest() throws Exception {
createAndStartJetty(legacyExampleCollection1SolrHome());
}
@Override
public void setUp() throws Exception {
super.setUp();
unexpected.clear();
}
public String getChain(Throwable th) {
StringBuilder sb = new StringBuilder(40);
Throwable lastCause = null;
do {
if (lastCause != null) sb.append("->");
sb.append(th.getClass().getSimpleName());
lastCause = th;
th = th.getCause();
} while(th != null);
sb.append("(" + lastCause.getMessage() + ")");
return sb.toString();
}
public void showExceptions() throws Exception {
if (unexpected.isEmpty()) return;
Map<String,Integer> counts = new HashMap<>();
// dedup in case there are many clients or many exceptions
for (Throwable e : unexpected) {
String chain = getChain(e);
Integer prev = counts.put(chain, 1);
if (prev != null) {
counts.put(chain, prev+1);
}
}
StringBuilder sb = new StringBuilder("EXCEPTION LIST:");
for (Map.Entry<String,Integer> entry : counts.entrySet()) {
sb.append("\n\t").append(entry.getValue()).append(") ").append(entry.getKey());
}
log.error("{}", sb);
}
@Test
public void testWithXml() throws Exception {
HttpSolrClient client = (HttpSolrClient) getSolrClient();
client.setRequestWriter(new RequestWriter());
client.deleteByQuery("*:*"); // delete everything!
doIt(client);
}
@Test
public void testWithBinary() throws Exception {
HttpSolrClient client = (HttpSolrClient) getSolrClient();
client.setRequestWriter(new BinaryRequestWriter());
client.deleteByQuery("*:*"); // delete everything!
doIt(client);
}
Iterator<SolrInputDocument> manyDocs(final int base, final int numDocs) {
return new Iterator<SolrInputDocument>() {
int count = 0;
@Override
public boolean hasNext() {
return count < numDocs;
}
@Override
public SolrInputDocument next() {
int id = base + count++;
if (count == 1) { // first doc is legit, and will increment a counter
return sdoc("id","test", "count_i", map("inc",1));
}
// include "ignore_exception" so the log doesn't fill up with known exceptions, and change the values for each doc
// so binary format won't compress too much
return sdoc("id",Integer.toString(id),"ignore_exception_field_does_not_exist_"+id,"fieldval"+id);
}
@Override
public void remove() {
}
};
};
void doThreads(final HttpSolrClient client, final int numThreads, final int numRequests) throws Exception {
final AtomicInteger tries = new AtomicInteger(0);
List<Thread> threads = new ArrayList<>();
for (int i=0; i<numThreads; i++) {
final int threadNum = i;
threads.add( new Thread() {
int reqLeft = numRequests;
@Override
public void run() {
try {
while (--reqLeft >= 0) {
tries.incrementAndGet();
doSingle(client, threadNum);
}
} catch (Throwable e) {
// Allow thread to exit, we should have already recorded the exception.
}
}
}
);
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
showExceptions();
int count = getCount(client);
if (count > tries.get()) {
fail("Number of requests was " + tries.get() + " but final count was " + count);
}
assertEquals(tries.get(), getCount(client));
assertTrue("got unexpected exceptions. ", unexpected.isEmpty() );
}
int getCount(HttpSolrClient client) throws IOException, SolrServerException {
client.commit();
QueryResponse rsp = client.query(params("q", "id:test", "fl", "count_i", "wt", "json"));
int count = ((Number)rsp.getResults().get(0).get("count_i")).intValue();
return count;
}
// this always failed with the Jetty 9.3 snapshot
void doIt(HttpSolrClient client) throws Exception {
client.deleteByQuery("*:*");
doThreads(client,10,100);
// doSingle(client, 1);
}
void doSingle(HttpSolrClient client, int threadNum) {
try {
client.add(manyDocs(threadNum*1000000, 1000));
}
catch (HttpSolrClient.RemoteSolrException e) {
String msg = e.getMessage();
assertTrue(msg, msg.contains("field_does_not_exist"));
}
catch (Throwable e) {
unexpected.add(e);
log.error("unexpected exception:", e);
fail("FAILING unexpected exception: " + e);
}
}
/***
@Test
public void testLive() throws Exception {
HttpSolrClient client = new HttpSolrClient("http://localhost:8983/techproducts/solr/");
client.add( sdoc() );
doiIt(client);
}
***/
String getJsonDocs(int numDocs) {
StringBuilder sb = new StringBuilder(numDocs * 20);
sb.append("[");
for (int i = 0; i < numDocs; i++) {
sb.append("{ id : '" + i + "' , unknown_field_" + i + " : 'unknown field value' }");
}
sb.append("]");
return sb.toString();
}
byte[] whitespace(int n) {
byte[] arr = new byte[n];
Arrays.fill(arr, (byte) ' ');
return arr;
}
String getResponse(InputStream is) throws Exception {
StringBuilder sb = new StringBuilder();
byte[] buf = new byte[100000];
for (;;) {
int n = 0;
try {
n = is.read(buf);
} catch (IOException e) {
// a real HTTP client probably wouldn't try to read past the end and would thus
// not get an exception until the *next* http request.
log.error("CAUGHT IOException, but already read {} : {}", sb.length(), getChain(e));
}
if (n <= 0) break;
sb.append(new String(buf, 0, n, StandardCharsets.UTF_8));
log.info("BUFFER={}", sb);
break; // for now, assume we got whole response in one read... otherwise we could block when trying to read again
}
return sb.toString();
}
@Test
public void testHttpURLConnection() throws Exception {
String bodyString = getJsonDocs(200000); // sometimes succeeds with this size, but larger can cause OOM from command line
HttpSolrClient client = (HttpSolrClient) getSolrClient();
String urlString = client.getBaseURL() + "/update";
HttpURLConnection conn = null;
URL url = new URL(urlString);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8);
writer.write(bodyString);
writer.flush();
int code = 1;
try {
code = conn.getResponseCode();
} catch (Throwable th) {
log.error("ERROR DURING conn.getResponseCode():", th);
}
/***
java.io.IOException: Error writing to server
at __randomizedtesting.SeedInfo.seed([2928C6EE314CD076:947A81A74F582526]:0)
at sun.net.www.protocol.http.HttpURLConnection.writeRequests(HttpURLConnection.java:665)
at sun.net.www.protocol.http.HttpURLConnection.writeRequests(HttpURLConnection.java:677)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1533)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1440)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
*/
log.info("CODE= {}", code);
InputStream is;
if (code == 200) {
is = conn.getInputStream();
} else {
log.info("Attempting to get error stream.");
is = conn.getErrorStream();
if (is == null) {
log.info("Can't get error stream... try input stream?");
is = conn.getInputStream();
}
}
String rbody = IOUtils.toString(is, StandardCharsets.UTF_8);
log.info("RESPONSE BODY:{}", rbody);
}
@Test
public void testRawSocket() throws Exception {
String hostName = "127.0.0.1";
int port = jetty.getLocalPort();
try (Socket socket = new Socket(hostName, port);
OutputStream out = new BufferedOutputStream(socket.getOutputStream());
InputStream in = socket.getInputStream();
) {
byte[] body = getJsonDocs(100000).getBytes(StandardCharsets.UTF_8);
int bodyLen = body.length;
// bodyLen *= 10; // make server wait for more
byte[] whitespace = whitespace(1000000);
bodyLen += whitespace.length;
String headers = "POST /solr/collection1/update HTTP/1.1\n" +
"Host: localhost:" + port + "\n" +
// "User-Agent: curl/7.43.0\n" +
"Accept: */*\n" +
"Content-type:application/json\n" +
"Content-Length: " + bodyLen + "\n" +
"Connection: Keep-Alive\n";
// Headers of HTTP connection are defined to be ASCII only:
out.write(headers.getBytes(StandardCharsets.US_ASCII));
out.write('\n'); // extra newline separates headers from body
out.write(body);
out.flush();
// Now what if I try to write more? This doesn't seem to throw an exception!
Thread.sleep(1000);
out.write(whitespace); // whitespace
out.flush();
String rbody = getResponse(in); // This will throw a connection reset exception if you try to read past the end of the HTTP response
log.info("RESPONSE BODY: {}", rbody);
assertTrue(rbody.contains("unknown_field"));
/***
// can I reuse now?
// writing another request doesn't actually throw an exception, but the following read does
out.write(headers);
out.write("\n"); // extra newline separates headers from body
out.write(body);
out.flush();
rbody = getResponse(in);
log.info("RESPONSE BODY: {}", rbody);
assertTrue(rbody.contains("unknown_field"));
***/
}
}
}