diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..54471f17 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +version: '2' + +services: + solr-base: + restart: "no" + build: + context: ./docker + ports: + - "8983:8983" + + solr-5.5: + extends: solr-base + build: + args: + PY_SOLR_VERSION: 5.5 + + solr-6.6: + extends: solr-base + build: + args: + PY_SOLR_VERSION: 6.6 + + solr-7.0: + extends: solr-base + build: + args: + PY_SOLR_VERSION: 7.0 + + solr-7.1: + extends: solr-base + build: + args: + PY_SOLR_VERSION: 7.1 diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..563295e3 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,15 @@ +ARG PY_SOLR_VERSION +FROM solr:${PY_SOLR_VERSION} + +ARG PY_SOLR_VERSION + +ENV CORES_DIR /opt/solr/server/solr + +USER root +COPY ./cores $CORES_DIR + +# rewrite solrconfig.xml luceneMatchVersion to match current version and re-chown +RUN perl -p -i -e "s|.*|${SOLR_VERSION}|"g ${CORES_DIR}/core0/conf/solrconfig.xml && \ + chown -R solr:solr $CORES_DIR + +USER solr diff --git a/docker/cores/core0/conf/currency.xml b/docker/cores/core0/conf/currency.xml new file mode 100644 index 00000000..654de41f --- /dev/null +++ b/docker/cores/core0/conf/currency.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docker/cores/core0/conf/elevate.xml b/docker/cores/core0/conf/elevate.xml new file mode 100644 index 00000000..8f3aa80f --- /dev/null +++ b/docker/cores/core0/conf/elevate.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + diff --git a/docker/cores/core0/conf/mapping-ISOLatin1Accent.txt b/docker/cores/core0/conf/mapping-ISOLatin1Accent.txt new file mode 100644 index 00000000..ede77425 --- /dev/null +++ b/docker/cores/core0/conf/mapping-ISOLatin1Accent.txt @@ -0,0 +1,246 @@ +# 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. + +# Syntax: +# "source" => "target" +# "source".length() > 0 (source cannot be empty.) +# "target".length() >= 0 (target can be empty.) + +# example: +# "À" => "A" +# "\u00C0" => "A" +# "\u00C0" => "\u0041" +# "ß" => "ss" +# "\t" => " " +# "\n" => "" + +# À => A +"\u00C0" => "A" + +# Á => A +"\u00C1" => "A" + +#  => A +"\u00C2" => "A" + +# à => A +"\u00C3" => "A" + +# Ä => A +"\u00C4" => "A" + +# Å => A +"\u00C5" => "A" + +# Æ => AE +"\u00C6" => "AE" + +# Ç => C +"\u00C7" => "C" + +# È => E +"\u00C8" => "E" + +# É => E +"\u00C9" => "E" + +# Ê => E +"\u00CA" => "E" + +# Ë => E +"\u00CB" => "E" + +# Ì => I +"\u00CC" => "I" + +# Í => I +"\u00CD" => "I" + +# Î => I +"\u00CE" => "I" + +# Ï => I +"\u00CF" => "I" + +# IJ => IJ +"\u0132" => "IJ" + +# Ð => D +"\u00D0" => "D" + +# Ñ => N +"\u00D1" => "N" + +# Ò => O +"\u00D2" => "O" + +# Ó => O +"\u00D3" => "O" + +# Ô => O +"\u00D4" => "O" + +# Õ => O +"\u00D5" => "O" + +# Ö => O +"\u00D6" => "O" + +# Ø => O +"\u00D8" => "O" + +# Œ => OE +"\u0152" => "OE" + +# Þ +"\u00DE" => "TH" + +# Ù => U +"\u00D9" => "U" + +# Ú => U +"\u00DA" => "U" + +# Û => U +"\u00DB" => "U" + +# Ü => U +"\u00DC" => "U" + +# Ý => Y +"\u00DD" => "Y" + +# Ÿ => Y +"\u0178" => "Y" + +# à => a +"\u00E0" => "a" + +# á => a +"\u00E1" => "a" + +# â => a +"\u00E2" => "a" + +# ã => a +"\u00E3" => "a" + +# ä => a +"\u00E4" => "a" + +# å => a +"\u00E5" => "a" + +# æ => ae +"\u00E6" => "ae" + +# ç => c +"\u00E7" => "c" + +# è => e +"\u00E8" => "e" + +# é => e +"\u00E9" => "e" + +# ê => e +"\u00EA" => "e" + +# ë => e +"\u00EB" => "e" + +# ì => i +"\u00EC" => "i" + +# í => i +"\u00ED" => "i" + +# î => i +"\u00EE" => "i" + +# ï => i +"\u00EF" => "i" + +# ij => ij +"\u0133" => "ij" + +# ð => d +"\u00F0" => "d" + +# ñ => n +"\u00F1" => "n" + +# ò => o +"\u00F2" => "o" + +# ó => o +"\u00F3" => "o" + +# ô => o +"\u00F4" => "o" + +# õ => o +"\u00F5" => "o" + +# ö => o +"\u00F6" => "o" + +# ø => o +"\u00F8" => "o" + +# œ => oe +"\u0153" => "oe" + +# ß => ss +"\u00DF" => "ss" + +# þ => th +"\u00FE" => "th" + +# ù => u +"\u00F9" => "u" + +# ú => u +"\u00FA" => "u" + +# û => u +"\u00FB" => "u" + +# ü => u +"\u00FC" => "u" + +# ý => y +"\u00FD" => "y" + +# ÿ => y +"\u00FF" => "y" + +# ff => ff +"\uFB00" => "ff" + +# fi => fi +"\uFB01" => "fi" + +# fl => fl +"\uFB02" => "fl" + +# ffi => ffi +"\uFB03" => "ffi" + +# ffl => ffl +"\uFB04" => "ffl" + +# ſt => ft +"\uFB05" => "ft" + +# st => st +"\uFB06" => "st" diff --git a/docker/cores/core0/conf/protwords.txt b/docker/cores/core0/conf/protwords.txt new file mode 100644 index 00000000..4341c051 --- /dev/null +++ b/docker/cores/core0/conf/protwords.txt @@ -0,0 +1,20 @@ +# 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. + +#----------------------------------------------------------------------- +# Use a protected word file to protect against the stemmer reducing two +# unrelated words to the same base word. + +# Some non-words that normally won't be encountered, +# just to test that they won't be stemmed. +dontstems +zwhacky diff --git a/docker/cores/core0/conf/schema.xml b/docker/cores/core0/conf/schema.xml new file mode 100644 index 00000000..923ea7c4 --- /dev/null +++ b/docker/cores/core0/conf/schema.xml @@ -0,0 +1,281 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docker/cores/core0/conf/solrconfig.xml b/docker/cores/core0/conf/solrconfig.xml new file mode 100644 index 00000000..da0bbccf --- /dev/null +++ b/docker/cores/core0/conf/solrconfig.xml @@ -0,0 +1,473 @@ + + + + + + 4.10.4 + + + + + + + + + + + + + + ${solr.data.dir:} + + + + ${solr.hdfs.home:} + ${solr.hdfs.confdir:} + ${solr.hdfs.blockcache.enabled:true} + ${solr.hdfs.blockcache.global:true} + + + + + + + + ${solr.lock.type:native} + true + + + + + + + ${solr.ulog.dir:} + + + + ${solr.autoCommit.maxTime:15000} + false + + + + ${solr.autoSoftCommit.maxTime:-1} + + + + + 1024 + + + + + + + true + + 20 + 200 + + + + + + + + static firstSearcher warming in solrconfig.xml + + + + + false + + 2 + + + + + + + + + + + explicit + 10 + text + + + + + + explicit + json + true + text + + + + + + true + json + true + + + + + + {!xport} + xsort + false + + + + query + + + + + + + + explicit + + velocity + browse + layout + Solritas + + edismax + + text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4 + title^10.0 description^5.0 keywords^5.0 author^2.0 resourcename^1.0 + + text + 100% + *:* + 10 + *,score + + + text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4 + title^10.0 description^5.0 keywords^5.0 author^2.0 resourcename^1.0 + + text,features,name,sku,id,manu,cat,title,description,keywords,author,resourcename + 3 + + on + true + cat + manu_exact + content_type + author_s + ipod + GB + 1 + cat,inStock + after + price + 0 + 600 + 50 + popularity + 0 + 10 + 3 + manufacturedate_dt + NOW/YEAR-10YEARS + NOW + +1YEAR + before + after + + on + content features title name + true + html + <b> + </b> + 0 + title + 0 + name + 3 + 200 + content + 750 + + on + false + 5 + 2 + 5 + true + true + 5 + 3 + + + + spellcheck + + + + + + + + true + ignored_ + true + links + ignored_ + + + + + + + + + + solrpingquery + + + all + + + + + + explicit + true + + + + + + + text_general + + + default + text + solr.DirectSolrSpellChecker + internal + 0.5 + 2 + 1 + 5 + 4 + 0.01 + + + + wordbreak + solr.WordBreakSolrSpellChecker + name + true + true + 10 + + + + + + text + default + wordbreak + on + true + 10 + 5 + 5 + true + true + 10 + 5 + + + spellcheck + + + + + + + + text + true + + + tvComponent + + + + + + lingo + org.carrot2.clustering.lingo.LingoClusteringAlgorithm + clustering/carrot2 + + + stc + org.carrot2.clustering.stc.STCClusteringAlgorithm + + + kmeans + org.carrot2.clustering.kmeans.BisectingKMeansClusteringAlgorithm + + + + + + true + true + name + id + features + true + false + edismax + + text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4 + + *:* + 10 + *,score + + + clustering + + + + + + + + true + false + + + terms + + + + + string + elevate.xml + + + + + explicit + text + + + elevator + + + + + + + + 100 + + + + + + 70 + 0.5 + [-\w ,/\n\"']{20,200} + + + + + + ]]> + ]]> + + + + + + + + + + + + + ,, + ,, + ,, + ,, + ,]]> + ]]> + + + + + + 10 + .,!? + + + + + + WORD + en + US + + + + + + + text/plain; charset=UTF-8 + + + + + + 5 + + + + *:* + + + diff --git a/docker/cores/core0/conf/spellings.txt b/docker/cores/core0/conf/spellings.txt new file mode 100644 index 00000000..162a044d --- /dev/null +++ b/docker/cores/core0/conf/spellings.txt @@ -0,0 +1,2 @@ +pizza +history diff --git a/docker/cores/core0/conf/stopwords.txt b/docker/cores/core0/conf/stopwords.txt new file mode 100644 index 00000000..ae1e83ee --- /dev/null +++ b/docker/cores/core0/conf/stopwords.txt @@ -0,0 +1,14 @@ +# 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. diff --git a/docker/cores/core0/conf/stopwords_en.txt b/docker/cores/core0/conf/stopwords_en.txt new file mode 100644 index 00000000..2c164c0b --- /dev/null +++ b/docker/cores/core0/conf/stopwords_en.txt @@ -0,0 +1,54 @@ +# 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. + +# a couple of test stopwords to test that the words are really being +# configured from this file: +stopworda +stopwordb + +# Standard english stop words taken from Lucene's StopAnalyzer +a +an +and +are +as +at +be +but +by +for +if +in +into +is +it +no +not +of +on +or +such +that +the +their +then +there +these +they +this +to +was +will +with diff --git a/docker/cores/core0/conf/synonyms.txt b/docker/cores/core0/conf/synonyms.txt new file mode 100644 index 00000000..0ef0e8da --- /dev/null +++ b/docker/cores/core0/conf/synonyms.txt @@ -0,0 +1,28 @@ +# 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. + +#----------------------------------------------------------------------- +#some test synonym mappings unlikely to appear in real input text +aaafoo => aaabar +bbbfoo => bbbfoo bbbbar +cccfoo => cccbar cccbaz +fooaaa,baraaa,bazaaa + +# Some synonym groups specific to this example +GB,gib,gigabyte,gigabytes +MB,mib,megabyte,megabytes +Television, Televisions, TV, TVs +#notice we use "gib" instead of "GiB" so any WordDelimiterFilter coming +#after us won't split it into two words. + +# Synonym mappings can be used for spelling correction too +pixima => pixma diff --git a/docker/cores/core0/core.properties b/docker/cores/core0/core.properties new file mode 100644 index 00000000..59f7c530 --- /dev/null +++ b/docker/cores/core0/core.properties @@ -0,0 +1 @@ +name=core0 diff --git a/docker/cores/solr.xml b/docker/cores/solr.xml new file mode 100644 index 00000000..d68cd6a9 --- /dev/null +++ b/docker/cores/solr.xml @@ -0,0 +1,45 @@ + + + + + + + + + ${host:} + ${jetty.port:8983} + ${hostContext:solr} + ${zkClientTimeout:30000} + ${genericCoreNodeNames:true} + + + + ${socketTimeout:0} + ${connTimeout:0} + + + diff --git a/pyproject.toml b/pyproject.toml index a425022c..8b01dc45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,4 +86,4 @@ lint.pylint.max-statements = 54 [tool.codespell] ignore-words-list = "dekstop,assertin,nwe,wahtever,yello" -skip = "./.*" +skip = "./.*,*.xml" diff --git a/start-solr-test-server.sh b/start-solr-test-server.sh index 3c2e08ce..4b96b566 100755 --- a/start-solr-test-server.sh +++ b/start-solr-test-server.sh @@ -7,7 +7,9 @@ if [ ! -t 0 ]; then exec 1>test-solr.stdout.log 2>test-solr.stderr.log fi -SOLR_VERSION=4.10.4 +# SOLR_VERSION=4.10.4 +DEFAULT_SOLR_VERSION=4.10.4 +SOLR_VERSION=${SOLR_VERSION:-4.10.4} ROOT=$(cd `dirname $0`; pwd) APP=$ROOT/solr-app @@ -167,40 +169,69 @@ function prepare() { prepare_core $ROOT/solr/cloud-configs cloud } +function prepare_docker() { + docker-compose build solr-${SOLR_VERSION} +} + +function stop_docker_solr() { + docker-compose stop +} + +function start_docker_solr() { + docker-compose up -d solr-${SOLR_VERSION} +} + if [ $# -eq 0 ]; then echo "$0 [prepare] [start] [stop]" exit fi while [ $# -gt 0 ]; do - if [ "$1" = "prepare" ]; then - prepare - elif [ "$1" = "stop" ]; then - stop_solr - elif [ "$1" = "start" ]; then - echo 'Starting Solr' - confirm_down non-cloud 8983 - confirm_down cloud-zk 8992 - confirm_down cloud-node0 8993 - confirm_down cloud-node1 8994 - - start_solr $ROOT/solr/cloud-zk-node 8992 zk -DzkRun - wait_for ZooKeeper 8992 - upload_configs localhost:9992 $ROOT/solr/cloud-configs/cloud/conf - - start_solr $ROOT/solr/non-cloud 8983 non-cloud - start_solr $ROOT/solr/cloud-node0 8993 cloud-node0 -DzkHost=localhost:9992 - start_solr $ROOT/solr/cloud-node1 8994 cloud-node1 -DzkHost=localhost:9992 - wait_for simple-solr 8983 - wait_for cloud-node0 8993 - wait_for cloud-node1 8994 - create_collection 8993 core0 localhost:8993_solr,localhost:8994_solr - create_collection 8993 core1 localhost:8993_solr,localhost:8994_solr - echo 'Solr started' + if [ "$SOLR_VERSION" = "$DEFAULT_SOLR_VERSION" ]; then + if [ "$1" = "prepare" ]; then + echo "Starting Solr ${SOLR_VERSION} locally" + prepare + elif [ "$1" = "stop" ]; then + echo 'Stopping Solr' + stop_solr + elif [ "$1" = "start" ]; then + echo 'Starting Solr' + confirm_down non-cloud 8983 + confirm_down cloud-zk 8992 + confirm_down cloud-node0 8993 + confirm_down cloud-node1 8994 + + start_solr $ROOT/solr/cloud-zk-node 8992 zk -DzkRun + wait_for ZooKeeper 8992 + upload_configs localhost:9992 $ROOT/solr/cloud-configs/cloud/conf + + start_solr $ROOT/solr/non-cloud 8983 non-cloud + start_solr $ROOT/solr/cloud-node0 8993 cloud-node0 -DzkHost=localhost:9992 + start_solr $ROOT/solr/cloud-node1 8994 cloud-node1 -DzkHost=localhost:9992 + wait_for simple-solr 8983 + wait_for cloud-node0 8993 + wait_for cloud-node1 8994 + create_collection 8993 core0 localhost:8993_solr,localhost:8994_solr + create_collection 8993 core1 localhost:8993_solr,localhost:8994_solr + echo 'Solr started' + else + echo "Unknown command: $1" + exit 1 + fi else - echo "Unknown command: $1" - exit 1 + if [ "$1" = "prepare" ]; then + echo "Starting Solr ${SOLR_VERSION} via Docker" + prepare_docker + elif [ "$1" = "stop" ]; then + echo 'Stopping Solr' + stop_docker_solr + elif [ "$1" = "start" ]; then + echo 'Starting Solr' + start_docker_solr + wait_for "Solr ${SOLR_VERSION}" 8983 10 + # Solr doesn't seem to be completely ready until ~1s after it lights up its port + sleep 2 + fi fi - shift done diff --git a/tests/test_admin.py b/tests/test_admin.py index 734a85e8..939aef2b 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -6,6 +6,8 @@ from pysolr import SolrCoreAdmin +from .utils import SolrVersion + class SolrCoreAdminTestCase(unittest.TestCase): def setUp(self): @@ -16,6 +18,10 @@ def test_status(self): self.assertIn('name="defaultCoreName"', self.solr_admin.status()) self.assertIn('', self.solr_admin.status(core="core0")) + @unittest.skipIf(SolrVersion() >= 5, "Solr 5+ does not use defaultCoreName") + def test_status_has_defaultCoreName(self): + self.assertTrue('name="defaultCoreName"' in self.solr_admin.status()) + def test_create(self): self.assertIn('0', self.solr_admin.create("wheatley")) diff --git a/tests/utils.py b/tests/utils.py index 0682ce90..5503fd69 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,7 +2,11 @@ from __future__ import absolute_import, unicode_literals +import os import subprocess # NOQA: B404 +from functools import total_ordering + +SOLR_VERSION = os.environ.get("SOLR_VERSION", "4.10.4") def _process(action): @@ -19,3 +23,53 @@ def start_solr(): def stop_solr(): _process("stop") + + +def solr_version_to_tuple(version): + """Given a string, integer, or tuple return a version tuple (x, y, z)""" + if isinstance(version, str): + version = version.split(".") + elif isinstance(version, int): + version = (version,) + + if len(version) < 2: + version += (0,) + + if len(version) < 3: + version += (0,) + + return tuple((int(v) for v in version)) + + +@total_ordering +class SolrVersion(object): + def __init__(self, version=SOLR_VERSION): + self.version = solr_version_to_tuple(version) + + def __eq__(self, other): + return self.version == solr_version_to_tuple(other) + + def __lt__(self, other): + return self.version < solr_version_to_tuple(other) + + +if __name__ == "__main__": + solr = SolrVersion("5.5") + assert solr < 6 + assert solr < "6" + assert solr < "6.0" + assert solr < "6.0.0" + assert solr < (6,) + assert solr < (6, 0) + assert solr < (6, 0, 0) + assert solr > 5 + assert solr > "5" + assert solr > "5.4" + assert solr > "5.4.2" + assert solr > (5, 4) + assert solr > "4.10.4" + assert solr > (4, 10, 4) + assert solr > () + assert solr > (0,) + assert solr > (0, 0) + assert solr > (0, 0, 1)