| /* |
| * 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.pkg; |
| |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import java.util.concurrent.Callable; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipOutputStream; |
| |
| import org.apache.commons.codec.digest.DigestUtils; |
| import org.apache.lucene.analysis.core.WhitespaceTokenizerFactory; |
| import org.apache.lucene.analysis.pattern.PatternReplaceCharFilterFactory; |
| import org.apache.lucene.util.ResourceLoader; |
| import org.apache.lucene.util.ResourceLoaderAware; |
| import org.apache.solr.client.solrj.SolrClient; |
| import org.apache.solr.client.solrj.SolrQuery; |
| 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; |
| import org.apache.solr.client.solrj.impl.HttpSolrClient; |
| import org.apache.solr.client.solrj.request.CollectionAdminRequest; |
| import org.apache.solr.client.solrj.request.GenericSolrRequest; |
| import org.apache.solr.client.solrj.request.RequestWriter; |
| import org.apache.solr.client.solrj.request.UpdateRequest; |
| import org.apache.solr.client.solrj.request.V2Request; |
| import org.apache.solr.client.solrj.request.beans.Package; |
| import org.apache.solr.client.solrj.response.QueryResponse; |
| import org.apache.solr.client.solrj.util.ClientUtils; |
| import org.apache.solr.cloud.MiniSolrCloudCluster; |
| import org.apache.solr.cloud.SolrCloudTestCase; |
| import org.apache.solr.common.MapWriterMap; |
| import org.apache.solr.common.NavigableObject; |
| import org.apache.solr.common.SolrInputDocument; |
| import org.apache.solr.common.annotation.JsonProperty; |
| import org.apache.solr.common.params.MapSolrParams; |
| import org.apache.solr.common.params.ModifiableSolrParams; |
| import org.apache.solr.common.params.SolrParams; |
| import org.apache.solr.common.util.ReflectMapWriter; |
| import org.apache.solr.common.util.Utils; |
| import org.apache.solr.core.SolrCore; |
| import org.apache.solr.filestore.PackageStoreAPI; |
| import org.apache.solr.filestore.TestDistribPackageStore; |
| import org.apache.solr.handler.RequestHandlerBase; |
| import org.apache.solr.request.SolrQueryRequest; |
| import org.apache.solr.response.SolrQueryResponse; |
| import org.apache.solr.search.QParser; |
| import org.apache.solr.search.QParserPlugin; |
| import org.apache.solr.util.LogLevel; |
| import org.apache.solr.util.SimplePostTool; |
| import org.apache.solr.util.plugin.SolrCoreAware; |
| import org.apache.zookeeper.data.Stat; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| import static org.apache.solr.common.cloud.ZkStateReader.SOLR_PKGS_PATH; |
| import static org.apache.solr.common.params.CommonParams.JAVABIN; |
| import static org.apache.solr.common.params.CommonParams.WT; |
| import static org.apache.solr.core.TestSolrConfigHandler.getFileContent; |
| import static org.apache.solr.filestore.TestDistribPackageStore.checkAllNodesForFile; |
| import static org.apache.solr.filestore.TestDistribPackageStore.readFile; |
| import static org.apache.solr.filestore.TestDistribPackageStore.uploadKey; |
| |
| @LogLevel("org.apache.solr.pkg.PackageLoader=DEBUG;org.apache.solr.pkg.PackageAPI=DEBUG") |
| public class TestPackages extends SolrCloudTestCase { |
| |
| @Before |
| public void setup() { |
| System.setProperty("enable.packages", "true"); |
| } |
| |
| @After |
| public void teardown() { |
| System.clearProperty("enable.packages"); |
| } |
| public static class ConfigPlugin implements ReflectMapWriter { |
| @JsonProperty |
| public String name; |
| |
| @JsonProperty("class") |
| public String klass; |
| } |
| @Test |
| @SuppressWarnings({"unchecked"}) |
| public void testPluginLoading() throws Exception { |
| MiniSolrCloudCluster cluster = |
| configureCluster(4) |
| .withJettyConfig(jetty -> jetty.enableV2(true)) |
| .addConfig("conf", configset("cloud-minimal")) |
| .configure(); |
| try { |
| String FILE1 = "/mypkg/runtimelibs.jar"; |
| String FILE2 = "/mypkg/runtimelibs_v2.jar"; |
| String FILE3 = "/mypkg/runtimelibs_v3.jar"; |
| String URP1 = "/mypkg/testurpv1.jar"; |
| String URP2 = "/mypkg/testurpv2.jar"; |
| String EXPR1 = "/mypkg/expressible.jar"; |
| String COLLECTION_NAME = "testPluginLoadingColl"; |
| byte[] derFile = readFile("cryptokeys/pub_key512.der"); |
| uploadKey(derFile, PackageStoreAPI.KEYS_DIR+"/pub_key512.der", cluster); |
| postFileAndWait(cluster, "runtimecode/runtimelibs.jar.bin", FILE1, |
| "L3q/qIGs4NaF6JiO0ZkMUFa88j0OmYc+I6O7BOdNuMct/xoZ4h73aZHZGc0+nmI1f/U3bOlMPINlSOM6LK3JpQ=="); |
| |
| postFileAndWait(cluster, "runtimecode/testurp_v1.jar.bin", URP1, |
| "h6UmMzuPqu4hQFGLBMJh/6kDSEXpJlgLsQDXx0KuxXWkV5giilRP57K3towiJRh2J+rqihqIghNCi3YgzgUnWQ=="); |
| |
| postFileAndWait(cluster, "runtimecode/expressible.jar.bin", EXPR1, |
| "ZOT11arAiPmPZYOHzqodiNnxO9pRyRozWZEBX8XGjU1/HJptFnZK+DI7eXnUtbNaMcbXE2Ze8hh4M/eGyhY8BQ=="); |
| |
| Package.AddVersion add = new Package.AddVersion(); |
| add.version = "1.0"; |
| add.pkg = "mypkg"; |
| add.files = Arrays.asList(new String[]{FILE1, URP1, EXPR1}); |
| V2Request req = new V2Request.Builder("/cluster/package") |
| .forceV2(true) |
| .withMethod(SolrRequest.METHOD.POST) |
| .withPayload(Collections.singletonMap("add", add)) |
| .build(); |
| |
| req.process(cluster.getSolrClient()); |
| |
| |
| CollectionAdminRequest |
| .createCollection(COLLECTION_NAME, "conf", 2, 2) |
| .process(cluster.getSolrClient()); |
| cluster.waitForActiveCollection(COLLECTION_NAME, 2, 4); |
| |
| TestDistribPackageStore.assertResponseValues(10, |
| () -> new V2Request.Builder("/cluster/package"). |
| withMethod(SolrRequest.METHOD.GET) |
| .build().process(cluster.getSolrClient()), |
| Utils.makeMap( |
| ":result:packages:mypkg[0]:version", "1.0", |
| ":result:packages:mypkg[0]:files[0]", FILE1 |
| )); |
| Map<String,ConfigPlugin> plugins = new LinkedHashMap<>(); |
| ConfigPlugin p = new ConfigPlugin(); |
| p.klass = "mypkg:org.apache.solr.core.RuntimeLibReqHandler"; |
| p.name = "/runtime"; |
| plugins.put("create-requesthandler", p); |
| |
| p = new ConfigPlugin(); |
| p.klass = "mypkg:org.apache.solr.core.RuntimeLibSearchComponent"; |
| p.name = "get"; |
| plugins.put("create-searchcomponent", p); |
| |
| p = new ConfigPlugin(); |
| p.klass = "mypkg:org.apache.solr.core.RuntimeLibResponseWriter"; |
| p.name = "json1"; |
| plugins.put("create-queryResponseWriter", p); |
| |
| p = new ConfigPlugin(); |
| p.klass = "mypkg:org.apache.solr.update.TestVersionedURP"; |
| p.name = "myurp"; |
| plugins.put("create-updateProcessor", p); |
| |
| p = new ConfigPlugin(); |
| p.klass = "mypkg:org.apache.solr.client.solrj.io.stream.metrics.MinCopyMetric"; |
| p.name = "mincopy"; |
| plugins.put("create-expressible", p); |
| |
| |
| V2Request v2r = new V2Request.Builder( "/c/"+COLLECTION_NAME+ "/config") |
| .withMethod(SolrRequest.METHOD.POST) |
| .withPayload(plugins) |
| .forceV2(true) |
| .build(); |
| cluster.getSolrClient().request(v2r); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "queryResponseWriter", "json1", |
| "mypkg", "1.0" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "searchComponent", "get", |
| "mypkg", "1.0" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "requestHandler", "/runtime", |
| "mypkg", "1.0" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "updateProcessor", "myurp", |
| "mypkg", "1.0" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "expressible", "mincopy", |
| "mypkg", "1.0" ); |
| |
| TestDistribPackageStore.assertResponseValues(10, |
| cluster.getSolrClient() , |
| new GenericSolrRequest(SolrRequest.METHOD.GET, |
| "/stream", new MapSolrParams((Map) Utils.makeMap("collection", COLLECTION_NAME, |
| WT, JAVABIN, |
| "action", "plugins" |
| ))), Utils.makeMap( |
| ":plugins:mincopy", "org.apache.solr.client.solrj.io.stream.metrics.MinCopyMetric" |
| )); |
| |
| UpdateRequest ur = new UpdateRequest(); |
| ur.add(new SolrInputDocument("id", "1")); |
| ur.setParam("processor", "myurp"); |
| ur.process(cluster.getSolrClient(), COLLECTION_NAME); |
| cluster.getSolrClient().commit(COLLECTION_NAME, true, true); |
| |
| QueryResponse result = cluster.getSolrClient() |
| .query(COLLECTION_NAME, new SolrQuery( "id:1")); |
| |
| assertEquals("Version 1", result.getResults().get(0).getFieldValue("TestVersionedURP.Ver_s")); |
| |
| executeReq( "/" + COLLECTION_NAME + "/runtime?wt=javabin", cluster.getRandomJetty(random()), |
| Utils.JAVABINCONSUMER, |
| Utils.makeMap("class", "org.apache.solr.core.RuntimeLibReqHandler")); |
| |
| executeReq( "/" + COLLECTION_NAME + "/get?wt=json", cluster.getRandomJetty(random()), |
| Utils.JSONCONSUMER, |
| Utils.makeMap("class", "org.apache.solr.core.RuntimeLibSearchComponent", |
| "Version","1")); |
| |
| |
| executeReq( "/" + COLLECTION_NAME + "/runtime?wt=json1", cluster.getRandomJetty(random()), |
| Utils.JSONCONSUMER, |
| Utils.makeMap("wt", "org.apache.solr.core.RuntimeLibResponseWriter")); |
| |
| //now upload the second jar |
| postFileAndWait(cluster, "runtimecode/runtimelibs_v2.jar.bin", FILE2, |
| "j+Rflxi64tXdqosIhbusqi6GTwZq8znunC/dzwcWW0/dHlFGKDurOaE1Nz9FSPJuXbHkVLj638yZ0Lp1ssnoYA=="); |
| |
| postFileAndWait(cluster, "runtimecode/testurp_v2.jar.bin", URP2, |
| "P/ptFXRvQMd4oKPvadSpd+A9ffwY3gcex5GVFVRy3df0/OF8XT5my8rQz7FZva+2ORbWxdXS8NKwNrbPVHLGXw=="); |
| //add the version using package API |
| add.version = "1.1"; |
| add.files = Arrays.asList(new String[]{FILE2,URP2, EXPR1}); |
| req.process(cluster.getSolrClient()); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "queryResponseWriter", "json1", |
| "mypkg", "1.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "searchComponent", "get", |
| "mypkg", "1.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "requestHandler", "/runtime", |
| "mypkg", "1.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "updateProcessor", "myurp", |
| "mypkg", "1.1" ); |
| |
| |
| executeReq( "/" + COLLECTION_NAME + "/get?wt=json", cluster.getRandomJetty(random()), |
| Utils.JSONCONSUMER, |
| Utils.makeMap( "Version","2")); |
| |
| |
| //now upload the third jar |
| postFileAndWait(cluster, "runtimecode/runtimelibs_v3.jar.bin", FILE3, |
| "a400n4T7FT+2gM0SC6+MfSOExjud8MkhTSFylhvwNjtWwUgKdPFn434Wv7Qc4QEqDVLhQoL3WqYtQmLPti0G4Q=="); |
| |
| add.version = "2.1"; |
| add.files = Arrays.asList(new String[]{FILE3, URP2, EXPR1}); |
| req.process(cluster.getSolrClient()); |
| |
| //now let's verify that the classes are updated |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "queryResponseWriter", "json1", |
| "mypkg", "2.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "searchComponent", "get", |
| "mypkg", "2.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "requestHandler", "/runtime", |
| "mypkg", "2.1" ); |
| |
| executeReq( "/" + COLLECTION_NAME + "/runtime?wt=json", cluster.getRandomJetty(random()), |
| Utils.JSONCONSUMER, |
| Utils.makeMap("Version","2")); |
| |
| //insert a doc with urp |
| ur = new UpdateRequest(); |
| ur.add(new SolrInputDocument("id", "2")); |
| ur.setParam("processor", "myurp"); |
| ur.process(cluster.getSolrClient(), COLLECTION_NAME); |
| cluster.getSolrClient().commit(COLLECTION_NAME, true, true); |
| |
| result = cluster.getSolrClient() |
| .query(COLLECTION_NAME, new SolrQuery( "id:2")); |
| |
| assertEquals("Version 2", result.getResults().get(0).getFieldValue("TestVersionedURP.Ver_s")); |
| |
| |
| Package.DelVersion delVersion = new Package.DelVersion(); |
| delVersion.pkg = "mypkg"; |
| delVersion.version = "1.0"; |
| V2Request delete = new V2Request.Builder("/cluster/package") |
| .withMethod(SolrRequest.METHOD.POST) |
| .forceV2(true) |
| .withPayload(Collections.singletonMap("delete", delVersion)) |
| .build(); |
| delete.process(cluster.getSolrClient()); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "queryResponseWriter", "json1", |
| "mypkg", "2.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "searchComponent", "get", |
| "mypkg", "2.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "requestHandler", "/runtime", |
| "mypkg", "2.1" ); |
| |
| // now remove the hughest version. So, it will roll back to the next highest one |
| delVersion.version = "2.1"; |
| delete.process(cluster.getSolrClient()); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "queryResponseWriter", "json1", |
| "mypkg", "1.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "searchComponent", "get", |
| "mypkg", "1.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "requestHandler", "/runtime", |
| "mypkg", "1.1" ); |
| |
| ModifiableSolrParams params = new ModifiableSolrParams(); |
| params.add("collection", COLLECTION_NAME); |
| new GenericSolrRequest(SolrRequest.METHOD.POST, "/config/params", params ){ |
| @Override |
| public RequestWriter.ContentWriter getContentWriter(String expectedType) { |
| return new RequestWriter.StringPayloadContentWriter("{set:{PKG_VERSIONS:{mypkg : '1.1'}}}", |
| ClientUtils.TEXT_JSON); |
| } |
| }.process(cluster.getSolrClient()) ; |
| |
| add.version = "2.1"; |
| add.files = Arrays.asList(new String[]{FILE3, URP2, EXPR1}); |
| req.process(cluster.getSolrClient()); |
| |
| //the collections mypkg is set to use version 1.1 |
| //so no upgrade |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "queryResponseWriter", "json1", |
| "mypkg", "1.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "searchComponent", "get", |
| "mypkg", "1.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "requestHandler", "/runtime", |
| "mypkg", "1.1" ); |
| |
| new GenericSolrRequest(SolrRequest.METHOD.POST, "/config/params", params ){ |
| @Override |
| public RequestWriter.ContentWriter getContentWriter(String expectedType) { |
| return new RequestWriter.StringPayloadContentWriter("{set:{PKG_VERSIONS:{mypkg : '2.1'}}}", |
| ClientUtils.TEXT_JSON); |
| } |
| }.process(cluster.getSolrClient()) ; |
| |
| //now, let's force every collection using 'mypkg' to refresh |
| //so that it uses version 2.1 |
| new V2Request.Builder("/cluster/package") |
| .withMethod(SolrRequest.METHOD.POST) |
| .withPayload("{refresh : mypkg}") |
| .forceV2(true) |
| .build() |
| .process(cluster.getSolrClient()); |
| |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "queryResponseWriter", "json1", |
| "mypkg", "2.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "searchComponent", "get", |
| "mypkg", "2.1" ); |
| |
| verifyComponent(cluster.getSolrClient(), |
| COLLECTION_NAME, "requestHandler", "/runtime", |
| "mypkg", "2.1" ); |
| |
| plugins.clear(); |
| p = new ConfigPlugin(); |
| p.name = "/rt_2"; |
| p.klass = "mypkg:"+ C.class.getName(); |
| plugins.put("create-requesthandler", p); |
| |
| p = new ConfigPlugin(); |
| p.name = "qp1"; |
| p.klass = "mypkg:"+ C2.class.getName(); |
| plugins.put("create-queryparser", p); |
| |
| v2r = new V2Request.Builder( "/c/"+COLLECTION_NAME+ "/config") |
| .withMethod(SolrRequest.METHOD.POST) |
| .withPayload(plugins) |
| .forceV2(true) |
| .build(); |
| cluster.getSolrClient().request(v2r); |
| assertTrue(C.informCalled); |
| assertTrue(C2.informCalled); |
| |
| //we create a new node. This node does not have the packages. But it should download it from another node |
| JettySolrRunner jetty = cluster.startJettySolrRunner(); |
| //create a new replica for this collection. it should end up |
| CollectionAdminRequest.addReplicaToShard(COLLECTION_NAME, "shard1") |
| .setNrtReplicas(1) |
| .setNode(jetty.getNodeName()) |
| .process(cluster.getSolrClient()); |
| cluster.waitForActiveCollection(COLLECTION_NAME, 2, 5); |
| checkAllNodesForFile(cluster,FILE3, |
| Utils.makeMap(":files:" + FILE3 + ":name", "runtimelibs_v3.jar"), |
| false); |
| } finally { |
| cluster.shutdown(); |
| } |
| } |
| @SuppressWarnings({"unchecked","rawtypes"}) |
| private void executeReq(String uri, JettySolrRunner jetty, Utils.InputStreamConsumer parser, Map expected) throws Exception { |
| try(HttpSolrClient client = (HttpSolrClient) jetty.newClient()){ |
| TestDistribPackageStore.assertResponseValues(10, |
| () -> { |
| Object o = Utils.executeGET(client.getHttpClient(), |
| jetty.getBaseUrl() + uri, parser); |
| if(o instanceof NavigableObject) return (NavigableObject) o; |
| if(o instanceof Map) return new MapWriterMap((Map) o); |
| throw new RuntimeException("Unknown response"); |
| }, expected); |
| |
| } |
| } |
| |
| private void verifyComponent(SolrClient client, String COLLECTION_NAME, |
| String componentType, String componentName, String pkg, String version) throws Exception { |
| @SuppressWarnings({"unchecked"}) |
| SolrParams params = new MapSolrParams((Map) Utils.makeMap("collection", COLLECTION_NAME, |
| WT, JAVABIN, |
| "componentName", componentName, |
| "meta", "true")); |
| |
| GenericSolrRequest req1 = new GenericSolrRequest(SolrRequest.METHOD.GET, |
| "/config/" + componentType, params); |
| TestDistribPackageStore.assertResponseValues(10, |
| client, |
| req1, Utils.makeMap( |
| ":config:" + componentType + ":" + componentName + ":_packageinfo_:package", pkg, |
| ":config:" + componentType + ":" + componentName + ":_packageinfo_:version", version |
| )); |
| } |
| |
| @Test |
| public void testAPI() throws Exception { |
| System.setProperty("enable.packages", "true"); |
| MiniSolrCloudCluster cluster = |
| configureCluster(4) |
| .withJettyConfig(jetty -> jetty.enableV2(true)) |
| .addConfig("conf", configset("cloud-minimal")) |
| .configure(); |
| try { |
| String errPath = "/error/details[0]/errorMessages[0]"; |
| String FILE1 = "/mypkg/v.0.12/jar_a.jar"; |
| String FILE2 = "/mypkg/v.0.12/jar_b.jar"; |
| String FILE3 = "/mypkg/v.0.13/jar_a.jar"; |
| |
| Package.AddVersion add = new Package.AddVersion(); |
| add.version = "0.12"; |
| add.pkg = "test_pkg"; |
| add.files = Arrays.asList(new String[]{FILE1, FILE2}); |
| V2Request req = new V2Request.Builder("/cluster/package") |
| .forceV2(true) |
| .withMethod(SolrRequest.METHOD.POST) |
| .withPayload(Collections.singletonMap("add", add)) |
| .build(); |
| |
| //the files is not yet there. The command should fail with error saying "No such file" |
| expectError(req, cluster.getSolrClient(), errPath, "No such file:"); |
| |
| |
| //post the jar file. No signature is sent |
| postFileAndWait(cluster, "runtimecode/runtimelibs.jar.bin", FILE1, null); |
| |
| |
| add.files = Arrays.asList(new String[]{FILE1}); |
| expectError(req, cluster.getSolrClient(), errPath, |
| FILE1 + " has no signature"); |
| //now we upload the keys |
| byte[] derFile = readFile("cryptokeys/pub_key512.der"); |
| uploadKey(derFile, PackageStoreAPI.KEYS_DIR+"/pub_key512.der", cluster); |
| //and upload the same file with a different name but it has proper signature |
| postFileAndWait(cluster, "runtimecode/runtimelibs.jar.bin", FILE2, |
| "L3q/qIGs4NaF6JiO0ZkMUFa88j0OmYc+I6O7BOdNuMct/xoZ4h73aZHZGc0+nmI1f/U3bOlMPINlSOM6LK3JpQ=="); |
| // with correct signature |
| //after uploading the file, let's delete the keys to see if we get proper error message |
| add.files = Arrays.asList(new String[]{FILE2}); |
| /*expectError(req, cluster.getSolrClient(), errPath, |
| "ZooKeeper does not have any public keys");*/ |
| |
| //Now lets' put the keys back |
| |
| //this time we have a file with proper signature, public keys are in ZK |
| // so the add {} command should succeed |
| req.process(cluster.getSolrClient()); |
| |
| //Now verify the data in ZK |
| TestDistribPackageStore.assertResponseValues(1, |
| () -> new MapWriterMap((Map) Utils.fromJSON(cluster.getZkClient().getData(SOLR_PKGS_PATH, |
| null, new Stat(), true))), |
| Utils.makeMap( |
| ":packages:test_pkg[0]:version", "0.12", |
| ":packages:test_pkg[0]:files[0]", FILE1 |
| )); |
| |
| //post a new jar with a proper signature |
| postFileAndWait(cluster, "runtimecode/runtimelibs_v2.jar.bin", FILE3, |
| "j+Rflxi64tXdqosIhbusqi6GTwZq8znunC/dzwcWW0/dHlFGKDurOaE1Nz9FSPJuXbHkVLj638yZ0Lp1ssnoYA=="); |
| |
| |
| //this time we are adding the second version of the package (0.13) |
| add.version = "0.13"; |
| add.pkg = "test_pkg"; |
| add.files = Arrays.asList(new String[]{FILE3}); |
| |
| //this request should succeed |
| req.process(cluster.getSolrClient()); |
| //no verify the data (/packages.json) in ZK |
| TestDistribPackageStore.assertResponseValues(1, |
| () -> new MapWriterMap((Map) Utils.fromJSON(cluster.getZkClient().getData(SOLR_PKGS_PATH, |
| null, new Stat(), true))), |
| Utils.makeMap( |
| ":packages:test_pkg[1]:version", "0.13", |
| ":packages:test_pkg[1]:files[0]", FILE3 |
| )); |
| |
| //Now we will just delete one version |
| Package.DelVersion delVersion = new Package.DelVersion(); |
| delVersion.version = "0.1";//this version does not exist |
| delVersion.pkg = "test_pkg"; |
| req = new V2Request.Builder("/cluster/package") |
| .forceV2(true) |
| .withMethod(SolrRequest.METHOD.POST) |
| .withPayload(Collections.singletonMap("delete", delVersion)) |
| .build(); |
| |
| //we are expecting an error |
| expectError(req, cluster.getSolrClient(), errPath, "No such version:"); |
| |
| delVersion.version = "0.12";//correct version. Should succeed |
| req.process(cluster.getSolrClient()); |
| //Verify with ZK that the data is correct |
| TestDistribPackageStore.assertResponseValues(1, |
| () -> new MapWriterMap((Map) Utils.fromJSON(cluster.getZkClient().getData(SOLR_PKGS_PATH, |
| null, new Stat(), true))), |
| Utils.makeMap( |
| ":packages:test_pkg[0]:version", "0.13", |
| ":packages:test_pkg[0]:files[0]", FILE2 |
| )); |
| |
| //So far we have been verifying the details with ZK directly |
| //use the package read API to verify with each node that it has the correct data |
| for (JettySolrRunner jetty : cluster.getJettySolrRunners()) { |
| String path = jetty.getBaseUrl().toString().replace("/solr", "/api") + "/cluster/package?wt=javabin"; |
| TestDistribPackageStore.assertResponseValues(10, new Callable<NavigableObject>() { |
| @Override |
| public NavigableObject call() throws Exception { |
| try (HttpSolrClient solrClient = (HttpSolrClient) jetty.newClient()) { |
| return (NavigableObject) Utils.executeGET(solrClient.getHttpClient(), path, Utils.JAVABINCONSUMER); |
| } |
| } |
| }, Utils.makeMap( |
| ":result:packages:test_pkg[0]:version", "0.13", |
| ":result:packages:test_pkg[0]:files[0]", FILE3 |
| )); |
| } |
| } finally { |
| cluster.shutdown(); |
| } |
| } |
| public static class C extends RequestHandlerBase implements SolrCoreAware { |
| static boolean informCalled = false; |
| |
| @Override |
| public void inform(SolrCore core) { |
| informCalled = true; |
| |
| } |
| |
| @Override |
| public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) { |
| |
| } |
| |
| @Override |
| public String getDescription() { |
| return "test"; |
| } |
| } |
| |
| public static class C2 extends QParserPlugin implements ResourceLoaderAware { |
| static boolean informCalled = false; |
| |
| |
| @Override |
| public void inform(ResourceLoader loader) throws IOException { |
| informCalled = true; |
| |
| } |
| |
| @Override |
| public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { |
| return null; |
| } |
| } |
| |
| public void testSchemaPlugins() throws Exception { |
| String COLLECTION_NAME = "testSchemaLoadingColl"; |
| System.setProperty("managed.schema.mutable", "true"); |
| |
| MiniSolrCloudCluster cluster = |
| configureCluster(4) |
| .withJettyConfig(jetty -> jetty.enableV2(true)) |
| .addConfig("conf1", configset("schema-package")) |
| .configure(); |
| try { |
| String FILE1 = "/schemapkg/schema-plugins.jar"; |
| byte[] derFile = readFile("cryptokeys/pub_key512.der"); |
| uploadKey(derFile, PackageStoreAPI.KEYS_DIR+"/pub_key512.der", cluster); |
| postFileAndWait(cluster, "runtimecode/schema-plugins.jar.bin", FILE1, |
| "U+AdO/jgY3DtMpeFRGoTQk72iA5g/qjPvdQYPGBaXB5+ggcTZk4FoIWiueB0bwGJ8Mg3V/elxOqEbD2JR8R0tA=="); |
| |
| String FILE2 = "/schemapkg/payload-component.jar"; |
| postFileAndWait(cluster, "runtimecode/payload-component.jar.bin", FILE2, |
| "gI6vYUDmSXSXmpNEeK1cwqrp4qTeVQgizGQkd8A4Prx2K8k7c5QlXbcs4lxFAAbbdXz9F4esBqTCiLMjVDHJ5Q=="); |
| |
| Package.AddVersion add = new Package.AddVersion(); |
| add.version = "1.0"; |
| add.pkg = "schemapkg"; |
| add.files = Arrays.asList(FILE1,FILE2); |
| V2Request req = new V2Request.Builder("/cluster/package") |
| .forceV2(true) |
| .withMethod(SolrRequest.METHOD.POST) |
| .withPayload(Collections.singletonMap("add", add)) |
| .build(); |
| req.process(cluster.getSolrClient()); |
| |
| TestDistribPackageStore.assertResponseValues(10, |
| () -> new V2Request.Builder("/cluster/package"). |
| withMethod(SolrRequest.METHOD.GET) |
| .build().process(cluster.getSolrClient()), |
| Utils.makeMap( |
| ":result:packages:schemapkg[0]:version", "1.0", |
| ":result:packages:schemapkg[0]:files[0]", FILE1 |
| )); |
| |
| CollectionAdminRequest |
| .createCollection(COLLECTION_NAME, "conf1", 2, 2) |
| .process(cluster.getSolrClient()); |
| cluster.waitForActiveCollection(COLLECTION_NAME, 2, 4); |
| |
| verifySchemaComponent(cluster.getSolrClient(), COLLECTION_NAME, "/schema/fieldtypes/myNewTextFieldWithAnalyzerClass", |
| Utils.makeMap(":fieldType:analyzer:charFilters[0]:_packageinfo_:version" ,"1.0", |
| ":fieldType:analyzer:tokenizer:_packageinfo_:version","1.0", |
| ":fieldType:_packageinfo_:version","1.0")); |
| |
| add = new Package.AddVersion(); |
| add.version = "2.0"; |
| add.pkg = "schemapkg"; |
| add.files = Arrays.asList(FILE1); |
| req = new V2Request.Builder("/cluster/package") |
| .forceV2(true) |
| .withMethod(SolrRequest.METHOD.POST) |
| .withPayload(Collections.singletonMap("add", add)) |
| .build(); |
| req.process(cluster.getSolrClient()); |
| |
| TestDistribPackageStore.assertResponseValues(10, |
| () -> new V2Request.Builder("/cluster/package"). |
| withMethod(SolrRequest.METHOD.GET) |
| .build().process(cluster.getSolrClient()), |
| Utils.makeMap( |
| ":result:packages:schemapkg[0]:version", "2.0", |
| ":result:packages:schemapkg[0]:files[0]", FILE1 |
| )); |
| |
| verifySchemaComponent(cluster.getSolrClient(), COLLECTION_NAME, "/schema/fieldtypes/myNewTextFieldWithAnalyzerClass", |
| Utils.makeMap(":fieldType:analyzer:charFilters[0]:_packageinfo_:version" ,"2.0", |
| ":fieldType:analyzer:tokenizer:_packageinfo_:version","2.0", |
| ":fieldType:_packageinfo_:version","2.0")); |
| |
| } finally { |
| cluster.shutdown(); |
| } |
| |
| } |
| @SuppressWarnings({"rawtypes","unchecked"}) |
| private void verifySchemaComponent(SolrClient client, String COLLECTION_NAME, String path, |
| Map expected) throws Exception { |
| SolrParams params = new MapSolrParams((Map) Utils.makeMap("collection", COLLECTION_NAME, |
| WT, JAVABIN, |
| "meta", "true")); |
| |
| GenericSolrRequest req = new GenericSolrRequest(SolrRequest.METHOD.GET,path |
| , params); |
| TestDistribPackageStore.assertResponseValues(10, |
| client, |
| req, expected); |
| } |
| |
| public static void postFileAndWait(MiniSolrCloudCluster cluster, String fname, String path, String sig) throws Exception { |
| ByteBuffer fileContent = getFileContent(fname); |
| String sha512 = DigestUtils.sha512Hex(fileContent.array()); |
| |
| TestDistribPackageStore.postFile(cluster.getSolrClient(), |
| fileContent, |
| path, sig);// has file, but no signature |
| |
| TestDistribPackageStore.checkAllNodesForFile(cluster, path, Utils.makeMap( |
| ":files:" + path + ":sha512", |
| sha512 |
| ), false); |
| } |
| |
| private void expectError(V2Request req, SolrClient client, String errPath, String expectErrorMsg) throws IOException, SolrServerException { |
| try { |
| req.process(client); |
| fail("should have failed with message : " + expectErrorMsg); |
| } catch (BaseHttpSolrClient.RemoteExecutionException e) { |
| String msg = e.getMetaData()._getStr(errPath, ""); |
| assertTrue("should have failed with message: " + expectErrorMsg + "actual message : " + msg, |
| msg.contains(expectErrorMsg) |
| ); |
| } |
| } |
| |
| public static class BasePatternReplaceCharFilterFactory extends PatternReplaceCharFilterFactory { |
| public BasePatternReplaceCharFilterFactory(Map<String, String> args) { |
| super(args); |
| } |
| } |
| |
| public static class BaseWhitespaceTokenizerFactory extends WhitespaceTokenizerFactory { |
| |
| public BaseWhitespaceTokenizerFactory(Map<String, String> args) { |
| super(args); |
| } |
| } |
| |
| /* |
| //copy the jav files to a package and then run the main method |
| public static void main(String[] args) throws Exception { |
| persistZip("/tmp/x.jar", MyPatternReplaceCharFilterFactory.class, MyTextField.class, MyWhitespaceTokenizerFactory.class); |
| }*/ |
| |
| |
| public static ByteBuffer persistZip(String loc, |
| @SuppressWarnings({"rawtypes"}) Class... classes) throws IOException { |
| ByteBuffer jar = generateZip(classes); |
| try (FileOutputStream fos = new FileOutputStream(loc)) { |
| fos.write(jar.array(), 0, jar.limit()); |
| fos.flush(); |
| } |
| return jar; |
| } |
| |
| public static ByteBuffer generateZip(@SuppressWarnings({"rawtypes"}) Class... classes) throws IOException { |
| SimplePostTool.BAOS bos = new SimplePostTool.BAOS(); |
| try (ZipOutputStream zipOut = new ZipOutputStream(bos)) { |
| zipOut.setLevel(ZipOutputStream.DEFLATED); |
| for (@SuppressWarnings({"rawtypes"}) Class c : classes) { |
| String path = c.getName().replace('.', '/').concat(".class"); |
| ZipEntry entry = new ZipEntry(path); |
| ByteBuffer b = SimplePostTool.inputStreamToByteArray(c.getClassLoader().getResourceAsStream(path)); |
| zipOut.putNextEntry(entry); |
| zipOut.write(b.array(), 0, b.limit()); |
| zipOut.closeEntry(); |
| } |
| } |
| return bos.getByteBuffer(); |
| } |
| |
| } |