blob: d6ef63dd06d7b006b57ba9202631d764d1d7e313 [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.filestore;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.client.methods.HttpDelete;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.impl.BaseHttpSolrClient.RemoteExecutionException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.V2Request;
import org.apache.solr.client.solrj.response.V2Response;
import org.apache.solr.cloud.MiniSolrCloudCluster;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.NavigableObject;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
import org.apache.solr.packagemanager.PackageUtils;
import org.apache.solr.util.LogLevel;
import org.apache.zookeeper.server.ByteBufferInputStream;
import org.junit.After;
import org.junit.Before;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
import static org.apache.solr.common.util.Utils.JAVABINCONSUMER;
import static org.apache.solr.core.TestSolrConfigHandler.getFileContent;
import static org.hamcrest.CoreMatchers.containsString;
@LogLevel("org.apache.solr.filestore.PackageStoreAPI=DEBUG;org.apache.solr.filestore.DistribPackageStore=DEBUG")
public class TestDistribPackageStore extends SolrCloudTestCase {
@Before
public void setup() {
System.setProperty("enable.packages", "true");
}
@After
public void teardown() {
System.clearProperty("enable.packages");
}
@SuppressWarnings({"unchecked"})
public void testPackageStoreManagement() throws Exception {
MiniSolrCloudCluster cluster =
configureCluster(4)
.withJettyConfig(jetty -> jetty.enableV2(true))
.addConfig("conf", configset("cloud-minimal"))
.configure();
try {
byte[] derFile = readFile("cryptokeys/pub_key512.der");
uploadKey(derFile, PackageStoreAPI.KEYS_DIR+"/pub_key512.der", cluster);
// cluster.getZkClient().makePath("/keys/exe", true);
// cluster.getZkClient().create("/keys/exe/pub_key512.der", derFile, CreateMode.PERSISTENT, true);
try {
postFile(cluster.getSolrClient(), getFileContent("runtimecode/runtimelibs.jar.bin"),
"/package/mypkg/v1.0/runtimelibs.jar",
"j+Rflxi64tXdqosIhbusqi6GTwZq8znunC/dzwcWW0/dHlFGKDurOaE1Nz9FSPJuXbHkVLj638yZ0Lp1ssnoYA=="
);
fail("should have failed because of wrong signature ");
} catch (RemoteExecutionException e) {
assertThat(e.getMessage(), containsString("Signature does not match"));
}
postFile(cluster.getSolrClient(), getFileContent("runtimecode/runtimelibs.jar.bin"),
"/package/mypkg/v1.0/runtimelibs.jar",
"L3q/qIGs4NaF6JiO0ZkMUFa88j0OmYc+I6O7BOdNuMct/xoZ4h73aZHZGc0+nmI1f/U3bOlMPINlSOM6LK3JpQ=="
);
assertResponseValues(10,
cluster.getSolrClient(),
new V2Request.Builder("/node/files/package/mypkg/v1.0")
.GET()
.build(),
Utils.makeMap(
":files:/package/mypkg/v1.0[0]:name", "runtimelibs.jar",
":files:/package/mypkg/v1.0[0]:sha512", "d01b51de67ae1680a84a813983b1de3b592fc32f1a22b662fc9057da5953abd1b72476388ba342cad21671cd0b805503c78ab9075ff2f3951fdf75fa16981420",
":files:/package/mypkg/v1.0[0]:sig[0]", "L3q/qIGs4NaF6JiO0ZkMUFa88j0OmYc+I6O7BOdNuMct/xoZ4h73aZHZGc0+nmI1f/U3bOlMPINlSOM6LK3JpQ=="
)
);
assertResponseValues(10,
cluster.getSolrClient(),
new V2Request.Builder("/node/files/package/mypkg")
.GET()
.build(),
Utils.makeMap(
":files:/package/mypkg[0]:name", "v1.0",
":files:/package/mypkg[0]:dir", "true"
)
);
@SuppressWarnings({"rawtypes"})
Map expected = Utils.makeMap(
":files:/package/mypkg/v1.0/runtimelibs.jar:name", "runtimelibs.jar",
":files:/package/mypkg/v1.0[0]:sha512", "d01b51de67ae1680a84a813983b1de3b592fc32f1a22b662fc9057da5953abd1b72476388ba342cad21671cd0b805503c78ab9075ff2f3951fdf75fa16981420"
);
checkAllNodesForFile(cluster,"/package/mypkg/v1.0/runtimelibs.jar", expected, true);
postFile(cluster.getSolrClient(), getFileContent("runtimecode/runtimelibs_v2.jar.bin"),
"/package/mypkg/v1.0/runtimelibs_v2.jar",
null
);
expected = Utils.makeMap(
":files:/package/mypkg/v1.0/runtimelibs_v2.jar:name", "runtimelibs_v2.jar",
":files:/package/mypkg/v1.0[0]:sha512",
"bc5ce45ad281b6a08fb7e529b1eb475040076834816570902acb6ebdd809410e31006efdeaa7f78a6c35574f3504963f5f7e4d92247d0eb4db3fc9abdda5d417"
);
checkAllNodesForFile(cluster,"/package/mypkg/v1.0/runtimelibs_v2.jar", expected, false);
expected = Utils.makeMap(
":files:/package/mypkg/v1.0", (Predicate<Object>) o -> {
@SuppressWarnings({"rawtypes"})
List l = (List) o;
assertEquals(2, l.size());
@SuppressWarnings({"rawtypes"})
Set expectedKeys = ImmutableSet.of("runtimelibs_v2.jar", "runtimelibs.jar");
for (Object file : l) {
if(! expectedKeys.contains(Utils.getObjectByPath(file, true, "name"))) return false;
}
return true;
}
);
for (JettySolrRunner jettySolrRunner : cluster.getJettySolrRunners()) {
String baseUrl = jettySolrRunner.getBaseUrl().toString().replace("/solr", "/api");
String url = baseUrl + "/node/files/package/mypkg/v1.0?wt=javabin";
assertResponseValues(10, new Fetcher(url, jettySolrRunner), expected);
}
// Delete Jars
DistribPackageStore.deleteZKFileEntry(cluster.getZkClient(), "/package/mypkg/v1.0/runtimelibs.jar");
JettySolrRunner j = cluster.getRandomJetty(random());
String path = j.getBaseURLV2() + "/cluster/files" + "/package/mypkg/v1.0/runtimelibs.jar";
HttpDelete del = new HttpDelete(path);
try(HttpSolrClient cl = (HttpSolrClient) j.newClient()) {
Utils.executeHttpMethod(cl.getHttpClient(), path, Utils.JSONCONSUMER, del);
}
expected = Collections.singletonMap(":files:/package/mypkg/v1.0/runtimelibs.jar", null);
checkAllNodesForFile(cluster,"/package/mypkg/v1.0/runtimelibs.jar", expected, false);
} finally {
cluster.shutdown();
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
public static void checkAllNodesForFile(MiniSolrCloudCluster cluster, String path, Map expected , boolean verifyContent) throws Exception {
for (JettySolrRunner jettySolrRunner : cluster.getJettySolrRunners()) {
String baseUrl = jettySolrRunner.getBaseUrl().toString().replace("/solr", "/api");
String url = baseUrl + "/node/files" + path + "?wt=javabin&meta=true";
assertResponseValues(10, new Fetcher(url, jettySolrRunner), expected);
if(verifyContent) {
try (HttpSolrClient solrClient = (HttpSolrClient) jettySolrRunner.newClient()) {
ByteBuffer buf = Utils.executeGET(solrClient.getHttpClient(), baseUrl + "/node/files" + path,
Utils.newBytesConsumer(Integer.MAX_VALUE));
assertEquals(
"d01b51de67ae1680a84a813983b1de3b592fc32f1a22b662fc9057da5953abd1b72476388ba342cad21671cd0b805503c78ab9075ff2f3951fdf75fa16981420",
DigestUtils.sha512Hex(new ByteBufferInputStream(buf))
);
}
}
}
}
@SuppressWarnings({"rawtypes"})
public static class Fetcher implements Callable {
String url;
JettySolrRunner jetty;
public Fetcher(String s, JettySolrRunner jettySolrRunner){
this.url = s;
this.jetty = jettySolrRunner;
}
@Override
public NavigableObject call() throws Exception {
try (HttpSolrClient solrClient = (HttpSolrClient) jetty.newClient()) {
return (NavigableObject) Utils.executeGET(solrClient.getHttpClient(), this.url, JAVABINCONSUMER);
}
}
@Override
public String toString() {
return url;
}
}
public static NavigableObject assertResponseValues(int repeats, SolrClient client,
@SuppressWarnings({"rawtypes"})SolrRequest req,
@SuppressWarnings({"rawtypes"})Map vals) throws Exception {
Callable<NavigableObject> callable = () -> req.process(client);
return assertResponseValues(repeats, callable,vals);
}
@SuppressWarnings({"unchecked"})
public static NavigableObject assertResponseValues(int repeats, Callable<NavigableObject> callable,
@SuppressWarnings({"rawtypes"})Map vals) throws Exception {
NavigableObject rsp = null;
for (int i = 0; i < repeats; i++) {
if (i > 0) {
Thread.sleep(100);
}
try {
rsp = callable.call();
} catch (Exception e) {
if (i >= repeats - 1) throw e;
continue;
}
for (Object e : vals.entrySet()) {
@SuppressWarnings({"rawtypes"})
Map.Entry entry = (Map.Entry) e;
String k = (String) entry.getKey();
List<String> key = StrUtils.split(k, '/');
Object val = entry.getValue();
@SuppressWarnings({"rawtypes"})
Predicate p = val instanceof Predicate ? (Predicate) val : o -> {
String v = o == null ? null : String.valueOf(o);
return Objects.equals(val, o);
};
boolean isPass = p.test(rsp._get(key, null));
if (isPass) return rsp;
else if (i >= repeats - 1) {
fail("req: " + callable.toString() +" . attempt: " + i + " Mismatch for value : '" + key + "' in response , " + Utils.toJSONString(rsp));
}
}
}
return rsp;
}
public static void uploadKey(byte[] bytes, String path, MiniSolrCloudCluster cluster) throws Exception {
JettySolrRunner jetty = cluster.getRandomJetty(random());
try(HttpSolrClient client = (HttpSolrClient) jetty.newClient()) {
PackageUtils.uploadKey(bytes, path, Paths.get(jetty.getCoreContainer().getSolrHome()), client);
Object resp = Utils.executeGET(client.getHttpClient(), jetty.getBaseURLV2().toString() + "/node/files" + path + "?sync=true", null);
System.out.println("sync resp: "+jetty.getBaseURLV2().toString() + "/node/files" + path + "?sync=true" + " ,is: " + resp);
}
checkAllNodesForFile(cluster,path, Utils.makeMap(":files:" + path + ":name", (Predicate<Object>) Objects::nonNull), false);
}
public static void postFile(SolrClient client, ByteBuffer buffer, String name, String sig)
throws SolrServerException, IOException {
String resource = "/cluster/files" + name;
ModifiableSolrParams params = new ModifiableSolrParams();
params.add("sig", sig);
V2Response rsp = new V2Request.Builder(resource)
.PUT()
.withPayload(buffer)
.forceV2(true)
.withMimeType("application/octet-stream")
.withParams(params)
.build()
.process(client);
assertEquals(name, rsp.getResponse().get(CommonParams.FILE));
}
/**
* Read and return the contents of the file-like resource
* @param fname the name of the resource to read
* @return the bytes of the resource
* @throws IOException if there is an I/O error reading the contents
*/
public static byte[] readFile(String fname) throws IOException {
try (InputStream is = TestDistribPackageStore.class.getClassLoader().getResourceAsStream(fname)) {
return is.readAllBytes();
}
}
}