You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by is...@apache.org on 2022/01/03 08:08:21 UTC

[solr] branch jira/SOLR15694 updated (6e97249 -> 60c4170)

This is an automated email from the ASF dual-hosted git repository.

ishan pushed a change to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git.


    omit 6e97249  cleanup and more test assertions
    omit f0b238f  addressed review comments
    omit 5d94773  Addressing review feedback
    omit 974ef77  Merge branch 'jira/SOLR15694' of https://gitbox.apache.org/repos/asf/solr into jira/SOLR15694
    omit 2d49e2e  Fix typo in reg guide
    omit 1b6db9b  rearranged API paths
    omit 9bff65e  Ref guide fixes
    omit 360e9ce  Merge branch 'jira/SOLR15694' of https://gitbox.apache.org/repos/asf/solr into jira/SOLR15694
    omit 0f3ff02  Adding Roles API to ref guide
    omit 1902f5f  Fixing precommit
    omit 5339e74  Merge branch 'jira/SOLR15694' of https://gitbox.apache.org/repos/asf/solr into jira/SOLR15694
    omit 24e5ba7  SOLR-15694: Ref guide changes
    omit fe421b4  removed unnecessary changes
    omit af96956  removed unnecessary changes
    omit 2a5485d  removed unnecessary changes
    omit 5e1bfa6  removed nocommit
    omit 41e85ad  clanup
    omit da9eb9e  supportedroles and overseer:disallowed supported
    omit b49e976  Adding defaultIfAbsent, minor formatting fixes
    omit e389530  new format
    omit 104cf95  unused import
    omit 85bc85e  refactor
    omit a16108b  Multiple roles supported per node
    omit 75cb680  tests were failing intermittently
    omit 01639c4  added test
    omit b2c7799  added APIs
    omit 69d8313  use a separate node
    omit c1da50b  suppress warnings
    omit 7ac8a62  cleanup
    omit 6e353a8  license header
    omit 4b082eb  Node role impl
     add 93c36a8  fixed esri link (#400)
     add 67c55b5  LUCENE-9229 fixed 3 javadoc links 1) google guave, 2&3) Two links relocated away from "aggregateknowledge" (#402)
     add 2d2d4c4  SOLR-10529 Revert change to solrconfig for techproducts example as it introduced test failures
     add 79f3d18  SOLR-15663: JsonFaceting benchmark should flush by doc count for better reproducible behavior. (#388)
     add 5c6d12d  Fix githubPRs.py to query the new solr repo (#405)
     add 1c80803   SOLR-15660: Remove universal 10 second test thread leak linger. (#377)
     add 401da35  SOLR-15711: Fix GCSRepository to follow createDirectory API contract (#373)
     add 2e5f89a  SOLR-15783: Prevent Logging MDC values from leaking between request threads, and set 'trace_id' in MDC as soon as it's available
     add 7646353  SOLR-15793: ref-guide jrubyPrepare fails due to gems trying to compile native code (#412)
     add aea698b  SOLR-15790: SearchHandler now includes the  value in the Logging MDC for the duration of the request, allowing custom logging configurations to include it.
     add 9ee30f1  SOLR-15791: Remove remaining <admin> clauses from solrconfigs (#411)
     add f088857  Switching a PRS collection from true -> false -> true results in INACTIVE replicas (#413)
     add dbdd2d4  SOLR-15635: don't repeat close hooks if SRI cleared twice due to using MDCThreadPool (#376)
     add 0f9e482  SOLR-15774: Avoid weird off-by-one errors with Angular's 'chosen' select box directive for the security and schema-designer screens in Admin UI (#404)
     add 81d3478  SOLR-15536: Rewrite /c/<coll>/shards v2 APIs using annotations (#218)
     add 9b1f8dd  SOLR-15792: Rewrite shardname APIs using annotation framework (#415)
     add 8479055  SOLR-14781: Delete unused classes (#330)
     add d18e247  SOLR-15619: add Long support for @JsonProperty annotations (#285)
     add c2dcba1  SOLR-15635: move to 8.11.1 in CHANGES.txt (#417)
     add ccb7d4d  DOAP changes for release 8.11.0
     add bfee656  SOLR-15784 Remove SolrJ dep on commons-io (#407)
     add 111bd91  SOLR-15785: Load custom endpoints from solr/lib (#408)
     add 70a19f9  SOLR-15795: Fix REPLACENODE to not use source node (#414)
     add be96b2b  a very embarrasing misspelling of the name of a contributor
     add c214e07  SOLR-15376: Accept "Long" values for CollectionAdminRequest.CreateTimeRoutedAlias.setMaxFutureMs to support durations greater than ~25 days. (#202)
     add a8290de  SOLR-13138: Remove deprecated spatial (#382)
     add 1ba86a8  SOLR-15806: expand UpdateLogCloudTest coverage w.r.t. Ephemeral vs. Standard DirectoryFactory use (#421)
     add 46d2ad2  SOLR-15804 Allow content-types with ;charset in ShowFileRequestHandler (#418)
     add e90f50f  SOLR-11623 Every request handler in Solr implement PermissionNameProvider (#372)
     add 5866f80  SOLR-15774: Move changes entry to 8.11.1 section
     add 3ec2fa0  SOLR-15808: The jattach tool for Docker image is now installed with apt (#423)
     add cee22de  SOLR-15700: Make include paths in bin/solr consistent (#358)
     add 55f6fbf  Revert "SOLR-11623 Every request handler in Solr implement PermissionNameProvider (#372)"
     add 5f34dbb  SOLR-15182: Remove orphaned apispec file
     add 058f2b6  Logging: SpellCheckComponent shouldn't log at info level (#422)
     add 0cb23c7  SOLR-15427: Nested docs: [child limit=...] now defaults to -1 (#144)
     add bf39765  SOLR-15182: replica API conversion to annotations (#429)
     add fc5fb26  Ref Guide: 8.11 upgrade notes
     new fa036ab  Node role impl
     new 277e415  license header
     new d694418  cleanup
     new 907d1f1  suppress warnings
     new 16e3ec8  use a separate node
     new 75d8410  added APIs
     new 71c11f9  added test
     new d4d98e5  tests were failing intermittently
     new 84982ef  Multiple roles supported per node
     new fa8059e  refactor
     new 0587057  unused import
     new 2716b06  new format
     new 072f659  Adding defaultIfAbsent, minor formatting fixes
     new ff26ae5  supportedroles and overseer:disallowed supported
     new e4e24dc  SOLR-15694: Ref guide changes
     new c941fd8  clanup
     new 4fb3713  removed nocommit
     new 525ff87  removed unnecessary changes
     new 1cf58d7  removed unnecessary changes
     new 37b216c  removed unnecessary changes
     new 6ffba2a  Adding Roles API to ref guide
     new 3c17462  Fixing precommit
     new a501b77  Ref guide fixes
     new 7ec11d5  Fix typo in reg guide
     new 483f984  rearranged API paths
     new 50f40a1  Addressing review feedback
     new 984edbf  addressed review comments
     new 60c4170  cleanup and more test assertions

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (6e97249)
            \
             N -- N -- N   refs/heads/jira/SOLR15694 (60c4170)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 28 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 dev-tools/doap/solr.rdf                            |   7 +
 dev-tools/scripts/githubPRs.py                     |  10 +-
 gradle/validation/forbidden-apis.gradle            |  23 +-
 gradle/validation/forbidden-apis/defaults.all.txt  |   6 +
 solr/CHANGES.txt                                   |  43 +-
 .../configs/cloud-minimal/conf/solrconfig.xml      |   3 +-
 .../solr/bench/MiniClusterBenchStateTest.java      |   2 +
 solr/bin/solr                                      |   6 +-
 .../apache/solr/schema/TestICUCollationField.java  |   7 +-
 .../schema/TestICUCollationFieldDocValues.java     |   8 +-
 .../analytics/function/reduction/MeanFunction.java |   7 -
 .../analytics/function/reduction/SumFunction.java  |   7 -
 .../solr/analytics/util/OldAnalyticsParams.java    |  34 --
 .../solr/analytics/facet/PivotFacetTest.java       |   2 +
 .../legacy/facet/LegacyFieldFacetCloudTest.java    |   2 +
 .../legacy/facet/LegacyRangeFacetCloudTest.java    |   3 +-
 .../util/OldAnalyticsRequestConverterUnitTest.java |   2 +
 .../solr/collection1/conf/solrconfig.xml           |  11 +-
 .../org/apache/solr/gcs/GCSBackupRepository.java   |  21 +-
 .../apache/solr/gcs/GCSIncrementalBackupTest.java  |   2 +
 .../apache/solr/jaeger/TestJaegerConfigurator.java |   2 +
 ...penNLPLangDetectUpdateProcessorFactoryTest.java |   2 +
 .../org/apache/solr/ltr/TestLTROnSolrCloud.java    |   2 +
 .../prometheus/scraper/SolrCloudScraperTest.java   |   2 +
 .../apache/solr/s3/S3IncrementalBackupTest.java    |   7 +-
 .../apache/solr/api/ContainerPluginsRegistry.java  |   6 +-
 .../apache/solr/cloud/api/collections/Assign.java  |  28 +-
 .../solr/cloud/api/collections/ReplaceNodeCmd.java | 151 ++---
 .../java/org/apache/solr/core/CoreContainer.java   |   1 +
 .../org/apache/solr/filestore/PackageStoreAPI.java |  14 +-
 .../org/apache/solr/handler/CollectionsAPI.java    |  10 +-
 .../apache/solr/handler/ContentStreamLoader.java   |  49 --
 .../solr/handler/admin/BaseHandlerApiSupport.java  |   4 +-
 .../solr/handler/admin/CollectionHandlerApi.java   |  77 ---
 .../solr/handler/admin/CollectionsHandler.java     |  17 +-
 .../solr/handler/admin/CoreAdminHandlerApi.java    |   4 +-
 .../solr/handler/admin/ShowFileRequestHandler.java |  10 +-
 ...dReplicaPropertyAPI.java => AddReplicaAPI.java} |  42 +-
 .../{MigrateDocsAPI.java => CreateShardAPI.java}   |  57 +-
 ...lectionStatusAPI.java => DeleteReplicaAPI.java} |  40 +-
 ...ollectionStatusAPI.java => DeleteShardAPI.java} |  46 +-
 .../solr/handler/admin/api/ForceLeaderAPI.java     |  68 +++
 .../handler/admin/api/ModifyCollectionAPI.java     |  10 +-
 .../{MigrateDocsAPI.java => SplitShardAPI.java}    |  49 +-
 .../solr/handler/admin/api/SyncShardAPI.java       |  68 +++
 .../org/apache/solr/handler/api/ApiRegistrar.java  |  11 +
 .../org/apache/solr/handler/api/V2ApiUtils.java}   |  19 +-
 .../solr/handler/component/SearchHandler.java      |  16 +-
 .../handler/component/SpellCheckComponent.java     |   6 +-
 .../java/org/apache/solr/legacy/BBoxStrategy.java  |   3 +-
 .../org/apache/solr/legacy/BBoxValueSource.java    |   1 +
 .../apache/solr/legacy/DistanceValueSource.java    | 120 ----
 .../apache/solr/legacy/PointVectorStrategy.java    | 289 ----------
 .../java/org/apache/solr/logging/MDCSnapshot.java  |  58 ++
 .../org/apache/solr/request/SolrRequestInfo.java   |  70 ++-
 .../apache/solr/response/RawResponseWriter.java    |   5 +-
 .../transform/ChildDocTransformerFactory.java      |  22 +-
 .../java/org/apache/solr/schema/GeoHashField.java  | 103 ----
 .../java/org/apache/solr/schema/LatLonType.java    | 606 ---------------------
 .../src/java/org/apache/solr/schema/PointType.java |   4 +-
 .../solr/schema/SpatialPointVectorFieldType.java   | 106 ----
 .../org/apache/solr/schema/SpatialQueryable.java   |   2 +-
 .../SpatialTermQueryPrefixTreeFieldType.java       |  33 --
 .../solr/search/SpatialFilterQParserPlugin.java    |   8 +-
 .../apache/solr/search/facet/FacetFieldMerger.java |  42 --
 .../distance/GeoDistValueSourceParser.java         |   2 +-
 .../search/function/distance/GeohashFunction.java  |   1 +
 .../distance/GeohashHaversineFunction.java         |   3 +-
 .../solr/search/stats/CachedSearcherStats.java     |  22 -
 .../java/org/apache/solr/servlet/HttpSolrCall.java |   7 +-
 .../apache/solr/servlet/SolrDispatchFilter.java    |  11 +-
 .../org/apache/solr/update/SolrCmdDistributor.java |   4 -
 .../HTMLStripFieldUpdateProcessorFactory.java      |   2 +-
 .../java/org/apache/solr/util/RedactionUtils.java  |  55 --
 .../src/java/org/apache/solr/util/hll/HLL.java     |   6 +-
 .../solr/collection1/conf/schema-hash.xml          |  28 -
 .../solr/collection1/conf/schema-minhash.xml       |  28 -
 .../solr/collection1/conf/schema-rest.xml          |  15 -
 .../solr/collection1/conf/schema-spatial.xml       |  23 +-
 .../solr/collection1/conf/schema-sql.xml           |  28 -
 .../test-files/solr/collection1/conf/schema.xml    |  21 +-
 .../test-files/solr/collection1/conf/schema11.xml  |  17 -
 .../test-files/solr/collection1/conf/schema12.xml  |  22 +-
 .../test-files/solr/collection1/conf/schema15.xml  |  18 +-
 .../solr/collection1/conf/schema_latest.xml        |  35 +-
 .../solr/collection1/conf/schemasurround.xml       |  17 -
 .../conf/solrconfig-analytics-query.xml            |   6 -
 .../conf/solrconfig-collapseqparser.xml            |   6 -
 .../conf/solrconfig-components-name.xml            |   7 +-
 .../conf/solrconfig-doctransformers.xml            |   6 -
 .../solr/collection1/conf/solrconfig-elevate.xml   |  11 +-
 .../solr/collection1/conf/solrconfig-hash.xml      |   6 -
 .../solr/collection1/conf/solrconfig-minhash.xml   |  30 +-
 .../collection1/conf/solrconfig-plugcollector.xml  |   5 -
 .../solr/collection1/conf/solrconfig-sql.xml       |   6 -
 .../solr/collection1/conf/solrconfig-test-misc.xml |   8 +-
 .../solr/collection1/conf/solrconfig.xml           |  34 +-
 .../solr/configsets/cloud-dynamic/conf/schema.xml  |  14 +-
 .../solr/configsets/doc-expiry/conf/schema.xml     |  18 -
 .../test-files/solr/configsets/sql/conf/schema.xml |  15 -
 .../src/test-files/solr/crazy-path-to-config.xml   |   5 -
 .../src/test/org/apache/hadoop/fs/HardLink.java    |  14 -
 .../test/org/apache/solr/TestCrossCoreJoin.java    |  27 +-
 .../test/org/apache/solr/TestGroupingSearch.java   |  14 +-
 .../apache/solr/cloud/CollectionsAPISolrJTest.java |   2 -
 .../cloud/HttpPartitionWithTlogReplicasTest.java   |   2 +
 .../org/apache/solr/cloud/ReplaceNodeTest.java     |  73 ++-
 .../apache/solr/cloud/TestCloudConsistency.java    |   2 +
 .../solr/cloud/TestLeaderElectionZkExpiry.java     |   8 +-
 .../org/apache/solr/cloud/TestStressLiveNodes.java |   2 +
 .../HdfsCloudIncrementalBackupTest.java            |   8 +-
 .../collections/TestHdfsCloudBackupRestore.java    |   2 +
 .../solr/cloud/hdfs/HDFSCollectionsAPITest.java    |   2 +
 .../cloud/hdfs/HdfsBasicDistributedZk2Test.java    |   2 +
 .../cloud/hdfs/HdfsBasicDistributedZkTest.java     |   2 +
 .../hdfs/HdfsChaosMonkeyNothingIsSafeTest.java     |   2 +
 .../cloud/hdfs/HdfsChaosMonkeySafeLeaderTest.java  |   2 +
 .../apache/solr/cloud/hdfs/HdfsNNFailoverTest.java |   2 +
 .../solr/cloud/hdfs/HdfsRecoverLeaseTest.java      |   2 +
 .../apache/solr/cloud/hdfs/HdfsRecoveryZkTest.java |   2 +
 .../cloud/hdfs/HdfsRestartWhileUpdatingTest.java   |   2 +
 .../apache/solr/cloud/hdfs/HdfsSyncSliceTest.java  |   2 +
 .../org/apache/solr/cloud/hdfs/HdfsTestUtil.java   |   2 +
 .../apache/solr/cloud/hdfs/HdfsThreadLeakTest.java |   2 +
 .../HdfsTlogReplayBufferedWhileIndexingTest.java   |   2 +
 .../cloud/hdfs/HdfsUnloadDistributedZkTest.java    |   2 +
 .../hdfs/HdfsWriteToMultipleCollectionsTest.java   |   2 +
 .../org/apache/solr/cloud/hdfs/StressHdfsTest.java |   2 +
 .../cluster/events/ClusterEventProducerTest.java   |   4 +-
 .../solr/core/BlobRepositoryMockingTest.java       |   4 +-
 .../solr/core/BlobStoreTestRequestHandler.java     |  62 ---
 .../apache/solr/core/HdfsDirectoryFactoryTest.java |   2 +
 .../test/org/apache/solr/core/MockInfoBean.java    |  65 ---
 .../src/test/org/apache/solr/core/TestConfig.java  |  30 +-
 .../HdfsBackupRepositoryIntegrationTest.java       |   2 +
 .../org/apache/solr/handler/TestBlobHandler.java   |   2 +
 .../solr/handler/TestHdfsBackupRestoreCore.java    |   2 +
 .../org/apache/solr/handler/TestRequestId.java     | 103 ++++
 .../solr/handler/admin/DaemonStreamApiTest.java    |   2 +
 .../solr/handler/admin/MBeansHandlerTest.java      |   3 +
 .../handler/admin/ShowFileRequestHandlerTest.java  |   7 +
 .../solr/handler/admin/TestApiFramework.java       |   5 +-
 .../solr/handler/admin/TestCollectionAPIs.java     |  30 +-
 .../admin/api/V2CollectionAPIMappingTest.java      |   2 +-
 .../handler/admin/api/V2ShardsAPIMappingTest.java  | 291 ++++++++++
 .../DistributedQueryComponentCustomSortTest.java   |   2 +
 .../solr/handler/component/UpdateLogCloudTest.java |  20 +-
 .../apache/solr/request/TestRemoteStreaming.java   |   2 +-
 .../apache/solr/request/TestSolrRequestInfo.java   |  80 +++
 .../solr/response/TestGeoJSONResponseWriter.java   |   9 +-
 .../transform/TestSubQueryTransformerDistrib.java  |   7 +-
 .../rest/schema/TestFieldCollectionResource.java   |  24 +-
 .../org/apache/solr/search/QueryEqualityTest.java  |  40 +-
 .../org/apache/solr/search/ReturnFieldsTest.java   |  22 +-
 .../org/apache/solr/search/SpatialFilterTest.java  |  21 +-
 .../org/apache/solr/search/TestSolr4Spatial.java   |   5 +-
 .../apache/solr/search/TestValueSourceCache.java   |   6 +-
 .../function/distance/DistanceFunctionTest.java    |  80 ++-
 .../apache/solr/store/hdfs/HdfsDirectoryTest.java  |   2 +
 .../AbstractAtomicUpdatesMultivalueTestBase.java   |   6 +-
 .../TimeRoutedAliasUpdateProcessorTest.java        |  43 ++
 .../solr/util/TestSolrJacksonAnnotation.java       |   6 +
 solr/docker/templates/Dockerfile.body.template     |   7 +-
 .../conf/solrconfig.xml                            |  10 -
 solr/solr-ref-guide/build.gradle                   |   3 +
 solr/solr-ref-guide/src/document-transformers.adoc |   4 +-
 .../src/field-types-included-with-solr.adoc        |   2 -
 .../src/partial-document-updates.adoc              |   2 +-
 solr/solr-ref-guide/src/solr-upgrade-notes.adoc    |  39 +-
 solr/solr-ref-guide/src/spatial-search.adoc        |  16 +-
 solr/solrj/build.gradle                            |   2 +-
 .../solr/client/solrj/impl/Http2SolrClient.java    |   8 +-
 .../solr/client/solrj/impl/HttpSolrClient.java     |   9 +-
 .../solr/client/solrj/impl/NoOpResponseParser.java |   6 +-
 .../client/solrj/impl/SolrHttpClientBuilder.java   |   5 -
 .../solr/client/solrj/request/ApiMapping.java      |  77 +++
 .../solrj/request/CollectionAdminRequest.java      |  15 +-
 .../client/solrj/request/CollectionApiMapping.java | 265 ---------
 .../solrj/request/ConfigSetAdminRequest.java       |   4 +-
 .../solrj/request/ContentStreamUpdateRequest.java  |   3 +-
 .../solr/client/solrj/request/CoreApiMapping.java  |   6 +-
 .../solrj/request/MultiContentWriterRequest.java   |   2 +-
 .../solrj/request/StreamingUpdateRequest.java      |   4 +-
 .../solr/client/solrj/request/V1toV2ApiMapper.java | 145 -----
 .../solr/client/solrj/request/V2Request.java       |   3 +-
 ...eReplicaPayload.java => AddReplicaPayload.java} |  47 +-
 .../solrj/request/beans/CreateAliasPayload.java    |   2 +-
 ...ReplicaPayload.java => CreateShardPayload.java} |  30 +-
 ...lectionPayload.java => ForceLeaderPayload.java} |  10 +-
 ...eReplicaPayload.java => SplitShardPayload.java} |  37 +-
 ...ollectionPayload.java => SyncShardPayload.java} |  10 +-
 .../apache/solr/common/EmptyEntityResolver.java    |   8 +-
 .../apache/solr/common/cloud/LiveNodesWatcher.java |  26 -
 .../apache/solr/common/cloud/PerReplicaStates.java |   4 +
 .../solr/common/cloud/PerReplicaStatesOps.java     |  20 +-
 .../org/apache/solr/common/cloud/SolrZkClient.java |   8 +-
 .../solr/common/params/CommonAdminParams.java      |   2 +
 .../apache/solr/common/util/JsonSchemaCreator.java |   2 +
 .../solr/common/util/JsonSchemaValidator.java      |  13 +
 .../solr/common/util/LinkedSimpleHashMap.java      |  40 --
 solr/solrj/src/java/org/noggit/CharArr.java        |  67 ---
 .../collections.collection.Commands.modify.json    |  16 -
 .../collections.collection.shards.Commands.json    | 129 -----
 ...llections.collection.shards.shard.Commands.json |  24 -
 ...collections.collection.shards.shard.delete.json |  27 -
 ...ons.collection.shards.shard.replica.delete.json |  39 --
 .../solrj/solr/collection1/conf/schema-sql.xml     |  28 -
 .../solrj/solr/collection1/conf/schema.xml         |  28 -
 .../solrj/solr/collection1/conf/solrconfig-sql.xml |   6 -
 .../solrj/solr/collection1/conf/solrconfig.xml     |  10 +-
 .../solrj/solr/configsets/ml/conf/solrconfig.xml   |   6 -
 .../solr/configsets/shared/conf/solrconfig.xml     |  10 +-
 .../solr/configsets/streaming/conf/schema.xml      |  31 +-
 .../solr/configsets/streaming/conf/solrconfig.xml  |   6 -
 .../solrj/solr/multicore/core0/conf/solrconfig.xml |  20 +-
 .../solrj/solr/multicore/core1/conf/solrconfig.xml |  20 +-
 .../client/solrj/io/stream/JDBCStreamTest.java     |   2 +
 .../solrj/io/stream/StreamExpressionTest.java      |   2 +
 .../client/solrj/request/TestV1toV2ApiMapper.java  |  41 --
 .../solr/client/solrj/request/TestV2Request.java   |   2 +
 .../cloud/PerReplicaStatesIntegrationTest.java     |  47 ++
 .../apache/solr/common/util/ContentStreamTest.java |  12 +-
 .../apache/solr/common/util/JsonValidatorTest.java |  23 +-
 .../solr/common/util/TestFastJavabinDecoder.java   |   3 +-
 .../apache/solr/EmbeddedSolrServerTestBase.java    |  32 +-
 .../org/apache/solr/SolrIgnoredThreadsFilter.java  |  33 +-
 .../java/org/apache/solr/SolrJettyTestBase.java    |  45 +-
 .../src/java/org/apache/solr/SolrTestCase.java     |   2 +-
 .../src/java/org/apache/solr/SolrTestCaseHS.java   |  12 +-
 .../src/java/org/apache/solr/SolrTestCaseJ4.java   |  72 ++-
 .../collections/AbstractIncrementalBackupTest.java |   5 +-
 .../solr/core/MockConcurrentMergeScheduler.java    |  34 --
 .../org/apache/solr/util/BadHdfsThreadsFilter.java |   2 +
 .../solr/util/BadMrClusterThreadsFilter.java       |  37 --
 .../java/org/apache/solr/util/DirectoryUtil.java   |  57 ++
 .../org/apache/solr/util/Log4jListAppender.java    |  79 +++
 .../src/java/org/apache/solr/util/TestHarness.java |  12 +-
 .../solr/cloud/MiniSolrCloudClusterTest.java       |   2 +
 .../org/apache/solr/util/TestErrorLogMuter.java    |  53 +-
 solr/webapp/web/js/angular/controllers/files.js    |   6 +-
 .../web/js/angular/controllers/schema-designer.js  |   9 +
 solr/webapp/web/js/angular/controllers/security.js |   7 +
 solr/webapp/web/partials/schema-designer.html      |  20 +-
 solr/webapp/web/partials/security.html             |   6 +-
 244 files changed, 2261 insertions(+), 3974 deletions(-)
 delete mode 100644 solr/core/src/java/org/apache/solr/handler/ContentStreamLoader.java
 delete mode 100644 solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
 copy solr/core/src/java/org/apache/solr/handler/admin/api/{AddReplicaPropertyAPI.java => AddReplicaAPI.java} (60%)
 copy solr/core/src/java/org/apache/solr/handler/admin/api/{MigrateDocsAPI.java => CreateShardAPI.java} (58%)
 copy solr/core/src/java/org/apache/solr/handler/admin/api/{CollectionStatusAPI.java => DeleteReplicaAPI.java} (56%)
 copy solr/core/src/java/org/apache/solr/handler/admin/api/{CollectionStatusAPI.java => DeleteShardAPI.java} (52%)
 create mode 100644 solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeaderAPI.java
 copy solr/core/src/java/org/apache/solr/handler/admin/api/{MigrateDocsAPI.java => SplitShardAPI.java} (57%)
 create mode 100644 solr/core/src/java/org/apache/solr/handler/admin/api/SyncShardAPI.java
 rename solr/core/src/{test/org/apache/solr/schema/ThrowErrorOnInitFieldType.java => java/org/apache/solr/handler/api/V2ApiUtils.java} (57%)
 delete mode 100644 solr/core/src/java/org/apache/solr/legacy/DistanceValueSource.java
 delete mode 100644 solr/core/src/java/org/apache/solr/legacy/PointVectorStrategy.java
 create mode 100644 solr/core/src/java/org/apache/solr/logging/MDCSnapshot.java
 delete mode 100644 solr/core/src/java/org/apache/solr/schema/GeoHashField.java
 delete mode 100644 solr/core/src/java/org/apache/solr/schema/LatLonType.java
 delete mode 100644 solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
 delete mode 100644 solr/core/src/java/org/apache/solr/schema/SpatialTermQueryPrefixTreeFieldType.java
 delete mode 100644 solr/core/src/java/org/apache/solr/search/stats/CachedSearcherStats.java
 delete mode 100644 solr/core/src/test/org/apache/solr/core/BlobStoreTestRequestHandler.java
 delete mode 100644 solr/core/src/test/org/apache/solr/core/MockInfoBean.java
 create mode 100644 solr/core/src/test/org/apache/solr/handler/TestRequestId.java
 create mode 100644 solr/core/src/test/org/apache/solr/handler/admin/api/V2ShardsAPIMappingTest.java
 create mode 100644 solr/core/src/test/org/apache/solr/request/TestSolrRequestInfo.java
 create mode 100644 solr/solrj/src/java/org/apache/solr/client/solrj/request/ApiMapping.java
 delete mode 100644 solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java
 delete mode 100644 solr/solrj/src/java/org/apache/solr/client/solrj/request/V1toV2ApiMapper.java
 copy solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/{MoveReplicaPayload.java => AddReplicaPayload.java} (55%)
 copy solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/{MoveReplicaPayload.java => CreateShardPayload.java} (71%)
 copy solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/{ReloadCollectionPayload.java => ForceLeaderPayload.java} (84%)
 copy solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/{MoveReplicaPayload.java => SplitShardPayload.java} (63%)
 copy solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/{ReloadCollectionPayload.java => SyncShardPayload.java} (84%)
 delete mode 100644 solr/solrj/src/java/org/apache/solr/common/cloud/LiveNodesWatcher.java
 delete mode 100644 solr/solrj/src/java/org/apache/solr/common/util/LinkedSimpleHashMap.java
 delete mode 100644 solr/solrj/src/resources/apispec/collections.collection.Commands.modify.json
 delete mode 100644 solr/solrj/src/resources/apispec/collections.collection.shards.Commands.json
 delete mode 100644 solr/solrj/src/resources/apispec/collections.collection.shards.shard.Commands.json
 delete mode 100644 solr/solrj/src/resources/apispec/collections.collection.shards.shard.delete.json
 delete mode 100644 solr/solrj/src/resources/apispec/collections.collection.shards.shard.replica.delete.json
 delete mode 100644 solr/solrj/src/test/org/apache/solr/client/solrj/request/TestV1toV2ApiMapper.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/core/MockConcurrentMergeScheduler.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/util/BadMrClusterThreadsFilter.java
 create mode 100644 solr/test-framework/src/java/org/apache/solr/util/DirectoryUtil.java
 create mode 100644 solr/test-framework/src/java/org/apache/solr/util/Log4jListAppender.java

[solr] 27/28: addressed review comments

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 984edbf8959a08392651c49aed302ffdec5338b3
Author: Noble Paul <no...@gmail.com>
AuthorDate: Wed Dec 22 23:19:52 2021 +1100

    addressed review comments
---
 .../apache/solr/cloud/api/collections/Assign.java  | 30 ++++++++--------------
 .../src/java/org/apache/solr/cluster/Cluster.java  |  1 +
 .../placement/impl/PlacementRequestImpl.java       |  2 +-
 .../impl/SimpleClusterAbstractionsImpl.java        | 15 ++++++++++-
 .../src/java/org/apache/solr/core/NodeRoles.java   | 17 +++++-------
 .../placement/ClusterAbstractionsForTest.java      |  5 ++++
 6 files changed, 39 insertions(+), 31 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
index 7dfcc33..6111b3c 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
@@ -21,18 +21,7 @@ import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP;
 
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Random;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 
 import org.apache.solr.client.solrj.cloud.DistribStateManager;
@@ -246,22 +235,25 @@ public class Assign {
         Collections.shuffle(nodeList, random);
       }
     } else {
-      nodeList = new ArrayList<>(liveNodes);
-      filterNonDataNodes(zk, nodeList);
+      nodeList = new ArrayList<>(filterNonDataNodes(zk, liveNodes));
       Collections.shuffle(nodeList, random);
     }
 
     return nodeList;
   }
 
-  public static void filterNonDataNodes(DistribStateManager zk, List<String> liveNodes) {
+  public static Collection<String> filterNonDataNodes(DistribStateManager zk, Collection<String> liveNodes) {
     try {
      List<String> noData =  ClusterAPI.getNodesByRole(NodeRoles.Role.DATA, NodeRoles.Mode.OFF, zk);
-     if (!noData.isEmpty()) {
-       liveNodes.removeAll(noData);
-     }
+      if (noData.isEmpty()) {
+        return liveNodes;
+      } else {
+        liveNodes  = new HashSet<>(liveNodes);
+        liveNodes.removeAll(noData);
+        return liveNodes;
+      }
     } catch (Exception e) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Problem fetching roles from Zookeeper", e);
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error fetching roles from Zookeeper", e);
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/cluster/Cluster.java b/solr/core/src/java/org/apache/solr/cluster/Cluster.java
index 3b7bdd4..4cd32a4 100644
--- a/solr/core/src/java/org/apache/solr/cluster/Cluster.java
+++ b/solr/core/src/java/org/apache/solr/cluster/Cluster.java
@@ -31,6 +31,7 @@ public interface Cluster {
    */
   Set<Node> getLiveNodes();
 
+  Set<Node> getLiveNodes(boolean filterNonDataNodes) ;
   /**
    * Returns info about the given collection if one exists.
    *
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/PlacementRequestImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/PlacementRequestImpl.java
index ff3f090..3814eb6 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/PlacementRequestImpl.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/PlacementRequestImpl.java
@@ -86,7 +86,7 @@ public class PlacementRequestImpl implements PlacementRequest {
         throw new Assign.AssignmentException("Bad assign request: empty list of nodes for collection " + solrCollection.getName());
       }
     } else {
-      nodes = cluster.getLiveNodes();
+      nodes = cluster.getLiveNodes(true);
       if (nodes.isEmpty()) {
         throw new Assign.AssignmentException("Impossible assign request: no live nodes for collection " + solrCollection.getName());
       }
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
index 48f3f50..64c0b64 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
@@ -23,6 +23,7 @@ import java.util.stream.Collectors;
 
 import com.google.common.collect.Maps;
 import org.apache.solr.client.solrj.cloud.SolrCloudManager;
+import org.apache.solr.cloud.api.collections.Assign;
 import org.apache.solr.cluster.*;
 import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
@@ -53,10 +54,16 @@ class SimpleClusterAbstractionsImpl {
 
   static class ClusterImpl implements Cluster {
     private final Set<Node> liveNodes;
+    private final Set<Node> liveNodesWithData;
     private final ClusterState clusterState;
 
     ClusterImpl(SolrCloudManager solrCloudManager) throws IOException {
-      liveNodes = NodeImpl.getNodes(solrCloudManager.getClusterStateProvider().getLiveNodes());
+      Set<String> liveNodes = solrCloudManager.getClusterStateProvider().getLiveNodes();
+      Collection<String> liveNodesWithData = Assign.filterNonDataNodes( solrCloudManager.getDistribStateManager(), liveNodes);
+      this.liveNodes = NodeImpl.getNodes(liveNodes);
+      this.liveNodesWithData = liveNodesWithData.size() == liveNodes.size() ?
+              this.liveNodes :
+              NodeImpl.getNodes(liveNodesWithData);
       clusterState = solrCloudManager.getClusterStateProvider().getClusterState();
     }
 
@@ -65,6 +72,12 @@ class SimpleClusterAbstractionsImpl {
       return liveNodes;
     }
 
+    public Set<Node> getLiveNodes(boolean filterNonDataNodes) {
+      return filterNonDataNodes ?
+              liveNodesWithData :
+              liveNodes;
+    }
+
     @Override
     public SolrCollection getCollection(String collectionName) {
       return SolrCollectionImpl.createCollectionFacade(clusterState, collectionName);
diff --git a/solr/core/src/java/org/apache/solr/core/NodeRoles.java b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
index 714d8b1..317f771 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeRoles.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
@@ -18,7 +18,6 @@ package org.apache.solr.core;
 
 import java.lang.invoke.MethodHandles;
 import java.util.*;
-import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.StringUtils;
 import org.apache.solr.common.cloud.ZkStateReader;
@@ -61,7 +60,7 @@ public class NodeRoles {
     }
     for(Role r: Role.values()) {
       if (!roles.containsKey(r)) {
-        roles.put(r, r.defaultIfAbsent());
+        roles.put(r, r.defaultMode());
       }
     }
     nodeRoles = Collections.unmodifiableMap(roles);
@@ -100,7 +99,7 @@ public class NodeRoles {
         return Set.of(Mode.ON, Mode.OFF);
       }
       @Override
-      public Mode defaultIfAbsent() {
+      public Mode defaultMode() {
         return Mode.OFF;
       }
     },
@@ -110,7 +109,7 @@ public class NodeRoles {
         return Set.of(Mode.ALLOWED, Mode.PREFERRED, Mode.DISALLOWED);
       }
       @Override
-      public Mode defaultIfAbsent() {
+      public Mode defaultMode() {
         return Mode.DISALLOWED;
       }
     };
@@ -122,12 +121,10 @@ public class NodeRoles {
     }
 
     public static Role getRole(String value) {
-      try {
-        Role role = Role.valueOf(value.toUpperCase(Locale.ROOT));
-        return role;
-      } catch (IllegalArgumentException ex) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown role: " + value);
+      for (Role role: Role.values()) {
+        if (value.equals(role.roleName)) return role;
       }
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown role: " + value);
     }
 
     public abstract Set<Mode> supportedModes();
@@ -135,7 +132,7 @@ public class NodeRoles {
     /**
      * Default mode for a role in nodes where this role is not specified.
      */
-    public abstract Mode defaultIfAbsent();
+    public abstract Mode defaultMode();
 
     @Override
     public String toString() {
diff --git a/solr/core/src/test/org/apache/solr/cluster/placement/ClusterAbstractionsForTest.java b/solr/core/src/test/org/apache/solr/cluster/placement/ClusterAbstractionsForTest.java
index 1260a10..bb85bd3 100644
--- a/solr/core/src/test/org/apache/solr/cluster/placement/ClusterAbstractionsForTest.java
+++ b/solr/core/src/test/org/apache/solr/cluster/placement/ClusterAbstractionsForTest.java
@@ -43,6 +43,11 @@ class ClusterAbstractionsForTest {
     }
 
     @Override
+    public Set<Node> getLiveNodes(boolean filterNonDataNodes) {
+      return liveNodes;
+    }
+
+    @Override
     public SolrCollection getCollection(String collectionName) {
       return collections.get(collectionName);
     }

[solr] 14/28: supportedroles and overseer:disallowed supported

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit ff26ae58f413155470c85e6a87d31d6d1dddad0a
Author: Noble Paul <no...@gmail.com>
AuthorDate: Sat Dec 11 11:20:54 2021 +1100

    supportedroles and overseer:disallowed supported
---
 .../java/org/apache/solr/cloud/ZkController.java   | 14 +++++++----
 .../src/java/org/apache/solr/core/NodeRoles.java   |  8 ++++++-
 .../java/org/apache/solr/handler/ClusterAPI.java   | 27 +++++++++++-----------
 .../test/org/apache/solr/cloud/NodeRolesTest.java  |  8 ++++++-
 4 files changed, 37 insertions(+), 20 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index 5e71098..33ea43d 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -352,7 +352,9 @@ public class ZkController implements Closeable {
 
                 overseerElector.setup(context);
 
-                overseerElector.joinElection(context, true);
+                if(cc.nodeRoles.isOverseerAllowed()) {
+                  overseerElector.joinElection(context, true);
+                }
               }
 
               cc.cancelCoreRecoveries();
@@ -683,7 +685,9 @@ public class ZkController implements Closeable {
         } finally {
 
           // just in case the OverseerElectionContext managed to start another Overseer
-          IOUtils.closeQuietly(overseer);
+          if(overseer != null) {
+            IOUtils.closeQuietly(overseer);
+          }
 
           ExecutorUtil.shutdownAndAwaitTermination(customThreadPool);
         }
@@ -909,14 +913,16 @@ public class ZkController implements Closeable {
       registerLiveNodesListener();
 
       // start the overseer first as following code may need it's processing
-      if (!zkRunOnly) {
+      if (!zkRunOnly &&  cc.nodeRoles.isOverseerAllowed()) {
         overseerElector = new LeaderElector(zkClient);
         this.overseer = new Overseer((HttpShardHandler) cc.getShardHandlerFactory().getShardHandler(), cc.getUpdateShardHandler(),
             CommonParams.CORES_HANDLER_PATH, zkStateReader, this, cloudConfig);
         ElectionContext context = new OverseerElectionContext(zkClient,
             overseer, getNodeName());
         overseerElector.setup(context);
-        overseerElector.joinElection(context, false);
+        if(cc.nodeRoles.isOverseerAllowed()) {
+          overseerElector.joinElection(context, false);
+        }
       }
 
       Stat stat = zkClient.exists(ZkStateReader.LIVE_NODES_ZKNODE, null, true);
diff --git a/solr/core/src/java/org/apache/solr/core/NodeRoles.java b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
index b72e5ec..4bb04e0 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeRoles.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
@@ -35,6 +35,7 @@ public class NodeRoles implements MapWriter {
   public static final String ALLOWED =  "allowed";
   public static final String DISALLOWED =  "disallowed";
   public static final String PREFERRED =  "preferred";
+  public static final Set<String> OVERSEER_MODES = Set.of(ALLOWED, PREFERRED, DISALLOWED);
 
   public static final String DEFAULT_ROLES_STRING = "data:on,overseer:allowed";
 
@@ -77,12 +78,17 @@ public class NodeRoles implements MapWriter {
     nodeRoles.forEach((role, s) -> ew.putNoEx(role.roleName, s));
   }
 
+  public boolean isOverseerAllowed() {
+    String roleMode = nodeRoles.get(Role.OVERSEER);
+    return ALLOWED.equals(roleMode) || PREFERRED.equals(roleMode);
+  }
+
   public enum Role {
     DATA("data"),
     OVERSEER("overseer") {
       @Override
       public Set<String> supportedModes() {
-        return Set.of(ALLOWED, PREFERRED, DISALLOWED);
+        return OVERSEER_MODES;
       }
       @Override
       public String defaultIfAbsent() {
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index 796ac49..446cd9b 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -84,41 +84,32 @@ public class ClusterAPI {
   @EndPoint(method = GET,
           path = "/cluster/node-roles",
           permission = COLL_READ_PERM)
-  @SuppressWarnings("unchecked")
   // nocommit: it must also output all data nodes that didn't start with -Dsolr.node.roles
   public void roles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     Map<String, Object> result = new LinkedHashMap<>();
 
-    rsp.add("node-roles", readRecursive(ZkStateReader.NODE_ROLES, collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager(), 3));
+    rsp.add("node-roles", readRecursive(ZkStateReader.NODE_ROLES,
+            collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager(), 3));
   }
 
   Object readRecursive(String path, DistribStateManager zk, int depth) {
     if (depth == 0) return null;
     Map<String, Object> result = null;
-    boolean hasSubValues = false;
     try {
       List<String> children = zk.listData(path);
       if (children != null && !children.isEmpty()) {
         result = new HashMap<>();
       } else {
-        return depth >=1 ? Collections.emptyList(): null;
+        return depth >= 1 ? Collections.emptyList() : null;
       }
       for (String child : children) {
         Object c = readRecursive(path + "/" + child, zk, depth - 1);
-        if (c != null) {
-          hasSubValues = true;
-        }
+
         result.put(child, c);
       }
 
-
     } catch (Exception e) {
-    }
-
-
-
-    if (result == null) {
-      return null;
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
     }
     if (depth == 1) {
       return result.keySet();
@@ -138,6 +129,14 @@ public class ClusterAPI {
                     collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager(), 2));
   }
   @EndPoint(method = GET,
+          path = "/cluster/node-roles/supported",
+          permission = COLL_READ_PERM)
+  public void supportedRoles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+    rsp.add("supported-roles",
+                    collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager().listData(ZkStateReader.NODE_ROLES));
+  }
+
+  @EndPoint(method = GET,
           path = "/cluster/node-roles/{role}/{role-val}",
           permission = COLL_READ_PERM)
   public void nodesWithRoleVal(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
diff --git a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
index a2e3651..cda83ad 100644
--- a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
@@ -21,6 +21,7 @@ import java.lang.invoke.MethodHandles;
 import java.util.Collection;
 import java.util.Collections;
 
+import java.util.List;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.V2Request;
@@ -50,9 +51,14 @@ public class NodeRolesTest extends SolrCloudTestCase {
   public void testRoleIntegration() throws Exception {
     JettySolrRunner j0 = cluster.getJettySolrRunner(0);
     JettySolrRunner j1 = null, j2 = null;
+    V2Response rsp = new V2Request.Builder("/cluster/node-roles/supported").GET().build().process(cluster.getSolrClient());
+    List<String> l = (List<String>) rsp._get("supported-roles", Collections.emptyList());
+    assertTrue(l.contains("data"));
+    assertTrue(l.contains("overseer"));
+
     j1 = startNodeWithRoles("overseer:preferred,data:off");
 
-    V2Response rsp = new V2Request.Builder("/cluster/node-roles").GET().build().process(cluster.getSolrClient());
+    rsp = new V2Request.Builder("/cluster/node-roles").GET().build().process(cluster.getSolrClient());
     assertEquals(j1.getNodeName(), rsp._getStr("node-roles/overseer/preferred[0]", null));
     assertEquals(j1.getNodeName(), rsp._getStr("node-roles/data/off[0]", null));
     OverseerRolesTest.waitForNewOverseer(20, j1.getNodeName(), false);

[solr] 01/28: Node role impl

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit fa036abc916bf92aceafcdb759fc2440c725191e
Author: Noble Paul <no...@gmail.com>
AuthorDate: Wed Nov 10 00:10:09 2021 +1100

    Node role impl
---
 .../java/org/apache/solr/cloud/ZkController.java   |  38 +++++--
 .../impl/SimpleClusterAbstractionsImpl.java        |  27 ++++-
 .../java/org/apache/solr/core/CoreContainer.java   |   2 +
 .../src/java/org/apache/solr/core/NodeRole.java    | 122 +++++++++++++++++++++
 .../test/org/apache/solr/core/TestNodeRole.java    |  53 +++++++++
 5 files changed, 234 insertions(+), 8 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index 75a4e83..8dee09e 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -46,6 +46,9 @@ import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 import com.google.common.base.Strings;
@@ -66,6 +69,7 @@ import org.apache.solr.common.cloud.*;
 import org.apache.solr.common.cloud.Replica.Type;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.IOUtils;
@@ -74,12 +78,7 @@ import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.URLUtil;
 import org.apache.solr.common.util.Utils;
-import org.apache.solr.core.CloseHook;
-import org.apache.solr.core.CloudConfig;
-import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.CoreDescriptor;
-import org.apache.solr.core.SolrCore;
-import org.apache.solr.core.SolrCoreInitializationException;
+import org.apache.solr.core.*;
 import org.apache.solr.handler.component.HttpShardHandler;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -105,6 +104,8 @@ import static org.apache.solr.common.cloud.ZkStateReader.ELECTION_NODE_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.NODE_NAME_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.REJOIN_AT_HEAD_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
+import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDROLE;
+import static org.apache.solr.common.params.CoreAdminParams.ACTION;
 import static org.apache.zookeeper.ZooDefs.Ids.OPEN_ACL_UNSAFE;
 
 /**
@@ -465,6 +466,7 @@ public class ZkController implements Closeable {
     });
 
     init();
+    persistRoles();
 
     if (distributedClusterStateUpdater.isDistributedStateUpdate()) {
       this.overseerJobQueue = null;
@@ -479,6 +481,28 @@ public class ZkController implements Closeable {
     assert ObjectReleaseTracker.track(this);
   }
 
+  @SuppressWarnings("unchecked")
+  private void persistRoles() throws InterruptedException {
+    try {
+      zkClient.atomicUpdate(ZkStateReader.ROLES,
+              bytes -> {
+                Map<String, Object> map = bytes == null ? null :
+                        (Map<String, Object>) Utils.fromJSON(bytes);
+                map = cc.nodeRole.modifyRoleData(map,
+                        getNodeName());
+                return map == null ? null : Utils.toJSON(map);
+              });
+      if (cc.nodeRole.role() == NodeRole.Type.overseer) {
+        getOverseerCollectionQueue()
+                .offer(Utils.toJSON(Utils.makeMap(ACTION, ADDROLE.name(),
+                        "role", NodeRole.Type.overseer,
+                        "node", getNodeName())));
+      }
+    } catch (KeeperException e) {
+      throw new SolrException(ErrorCode.SERVER_ERROR, "can't update roles");
+    }
+  }
+
   /**
    * <p>Verifies if /clusterstate.json exists in Zookeepeer, and if it does and is not empty, refuses to start and outputs
    * a helpful message regarding collection migration.</p>
@@ -2198,7 +2222,7 @@ public class ZkController implements Closeable {
       List<?> nodeList = (List<?>) roles.get("overseer");
       if (nodeList == null) return;
       if (nodeList.contains(getNodeName())) {
-        ZkNodeProps props = new ZkNodeProps(Overseer.QUEUE_OPERATION, CollectionParams.CollectionAction.ADDROLE.toString().toLowerCase(Locale.ROOT),
+        ZkNodeProps props = new ZkNodeProps(Overseer.QUEUE_OPERATION, ADDROLE.toString().toLowerCase(Locale.ROOT),
             "node", getNodeName(),
             "role", "overseer");
         log.info("Going to add role {} ", props);
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
index 48f3f50..1d5bec6 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
@@ -23,12 +23,18 @@ import java.util.stream.Collectors;
 
 import com.google.common.collect.Maps;
 import org.apache.solr.client.solrj.cloud.SolrCloudManager;
+import org.apache.solr.client.solrj.cloud.VersionedData;
 import org.apache.solr.cluster.*;
+import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Slice;
+import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.CollectionAdminParams;
 import org.apache.solr.common.util.Pair;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.NodeRole;
+import org.apache.zookeeper.KeeperException;
 
 import javax.annotation.Nonnull;
 
@@ -56,7 +62,26 @@ class SimpleClusterAbstractionsImpl {
     private final ClusterState clusterState;
 
     ClusterImpl(SolrCloudManager solrCloudManager) throws IOException {
-      liveNodes = NodeImpl.getNodes(solrCloudManager.getClusterStateProvider().getLiveNodes());
+      Set<String> liveNodes = solrCloudManager.getClusterStateProvider().getLiveNodes();
+      try {
+        if (solrCloudManager.getDistribStateManager().hasData(ZkStateReader.ROLES)) {
+          VersionedData rolesData = solrCloudManager.getDistribStateManager().getData(ZkStateReader.ROLES);
+          if (rolesData != null && rolesData.getData().length > 0) {
+            Map<String, Object> roles = (Map<String, Object>) Utils.fromJSON(rolesData.getData());
+            List<String> noReplicasNodes = (List<String>) roles.get(NodeRole.NO_REPLICAS);
+            if (noReplicasNodes != null && !noReplicasNodes.isEmpty()) {
+              liveNodes = new HashSet<>(liveNodes);
+              liveNodes.removeAll(noReplicasNodes);
+            }
+          }
+        }
+
+      } catch (NoSuchElementException nsee) {
+        //no problem
+      } catch (Exception e) {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to communicate with Zookeeper");
+      }
+      this.liveNodes = NodeImpl.getNodes(liveNodes);
       clusterState = solrCloudManager.getClusterStateProvider().getClusterState();
     }
 
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index d4d2030..f2c2b78 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -246,6 +246,8 @@ public class CoreContainer {
 
   private final ObjectCache objectCache = new ObjectCache();
 
+  public final NodeRole nodeRole = new NodeRole(System.getProperty("node.roles"));
+
   private final ClusterSingletons clusterSingletons = new ClusterSingletons(
       () -> getZkController() != null &&
           getZkController().getOverseer() != null &&
diff --git a/solr/core/src/java/org/apache/solr/core/NodeRole.java b/solr/core/src/java/org/apache/solr/core/NodeRole.java
new file mode 100644
index 0000000..1de7832
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/core/NodeRole.java
@@ -0,0 +1,122 @@
+/*
+ * 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.core;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.*;
+import org.apache.solr.common.StringUtils;
+import org.apache.solr.common.util.StrUtils;
+
+public class NodeRole {
+  private boolean hasData;
+  private Type role;
+
+  public NodeRole(String role) {
+    if (StringUtils.isEmpty(role)) {
+      hasData = true;
+      this.role = Type.data;
+      return;
+    }
+    Set<String> roles = new HashSet<>(StrUtils.split(role, ','));
+    if (roles.isEmpty()) {
+      hasData = true;
+      return;
+    }
+    for (String s : roles) {
+      if (Type.data.name().equals(s)) {
+        hasData = true;
+        continue;
+      }
+      if (Type.overseer.name().equals(s)) {
+        this.role = Type.overseer;
+      } else {
+        throw new RuntimeException("Unknown type");
+      }
+    }
+
+  }
+
+  public boolean hasData() {
+    return hasData;
+  }
+
+  public Type role() {
+    return role;
+  }
+
+  @SuppressWarnings("unchecked")
+  public Map<String, Object> modifyRoleData(Map<String, Object> rolesData, String nodeName) {
+    if (this.role == Type.data) {
+      //this is a normal data node, nothing to do
+      if (rolesData == null) return null;// no role info, return
+      if (removeStaleRoles(nodeName, rolesData, Collections.emptySet())) {
+        return rolesData;
+      } else {
+        return null;
+      }
+    } else {
+      if (rolesData == null) rolesData = new HashMap<>();
+      List<String> nodes = (List<String>) rolesData.computeIfAbsent(role.name(), s -> new ArrayList<String>());
+      boolean isModified = false;
+      if (!nodes.contains(nodeName)) {
+        nodes.add(nodeName);
+        isModified = true;
+      }
+      List<String> nonReplicaNodes = (List<String>) rolesData.computeIfAbsent(NO_REPLICAS, s -> new ArrayList<String>());
+      if (hasData) {
+        if(nonReplicaNodes.contains(nodeName)) {
+          nonReplicaNodes.remove(nodeName);
+          isModified = true;
+        }
+      } else {
+        if(!nonReplicaNodes.contains(nodeName)){
+          nonReplicaNodes.add(nodeName);
+          isModified = true;
+        }
+      }
+      if (isModified || removeStaleRoles(nodeName, rolesData, ImmutableSet.of(role.name(), NO_REPLICAS))) {
+        return rolesData;
+      } else {
+        return null;
+      }
+    }
+
+  }
+
+  @SuppressWarnings("unchecked")
+  private boolean removeStaleRoles(String nodeName, Map<String, Object> rolesData, Set<String> ignore) {
+    boolean[] isModified = new boolean[]{false};
+    rolesData.forEach((s, o) -> {
+      if (ignore.contains(s)) return;
+      if (o instanceof List) {
+        List<String> list = (List<String>) o;
+        if (list.contains(nodeName)) {
+          isModified[0] = true;
+          list.remove(nodeName);
+        }
+      }
+    });
+    return isModified[0];
+  }
+
+  public enum Type {
+    data, overseer;
+  }
+
+  public static final String NO_REPLICAS = "no-replicas";
+
+}
diff --git a/solr/core/src/test/org/apache/solr/core/TestNodeRole.java b/solr/core/src/test/org/apache/solr/core/TestNodeRole.java
new file mode 100644
index 0000000..d5bdba5
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/core/TestNodeRole.java
@@ -0,0 +1,53 @@
+package org.apache.solr.core;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.Map;
+import org.apache.solr.SolrTestCase;
+import org.apache.solr.common.util.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestNodeRole extends SolrTestCase {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+
+  @SuppressWarnings("rawtypes")
+  public void testZkData() {
+    //empty
+    Map<String,Object> rolesData = null;
+    //start a node with overseer role
+    rolesData = new NodeRole("overseer").modifyRoleData(rolesData, "node1");
+    assertEquals("node1", _val(rolesData,  "overseer[0]"));
+    assertEquals("node1", _val(rolesData,  "no-replicas[0]"));
+    //now start another node with overseer
+    rolesData = new NodeRole("overseer").modifyRoleData(rolesData, "node2");
+    assertEquals("node2", _val(rolesData,  "overseer[1]"));
+    assertEquals("node2", _val(rolesData,  "no-replicas[1]"));
+    //now start another node with overseer,data
+    rolesData = new NodeRole("overseer,data").modifyRoleData(rolesData, "node3");
+
+    assertEquals("node3", _val(rolesData, "overseer[2]"));
+    assertEquals(2, ((Collection) _val(rolesData, "no-replicas")).size() );
+    //now restart node2 with no role
+
+    rolesData = new NodeRole(null).modifyRoleData(rolesData, "node2");
+    assertEquals("node3", _val(rolesData, "overseer[1]"));
+    assertEquals(1, ((Collection) _val(rolesData, "no-replicas")).size() );
+    //now restart node1 with the original values
+    assertNull(new NodeRole("overseer").modifyRoleData(rolesData, "node1"));
+
+    assertEquals("node1", _val(rolesData,  "overseer[0]"));
+    assertEquals("node1", _val(rolesData,  "no-replicas[0]"));
+    //now restart node1 with overseer,data
+    rolesData =  new NodeRole("overseer,data").modifyRoleData(rolesData, "node1");
+    assertEquals("node1", _val(rolesData,  "overseer[0]"));
+    assertEquals(0, ((Collection) _val(rolesData, "no-replicas")).size() );
+
+    log.info("rolesdata : {}", Utils.toJSONString(rolesData));
+  }
+
+  private Object _val(Map<String, Object> rolesData, String path) {
+    return Utils.getObjectByPath(rolesData, false, path);
+  }
+}

[solr] 12/28: new format

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 2716b06007ad7ff541a9c60b17965251a9420fa4
Author: Noble Paul <no...@gmail.com>
AuthorDate: Fri Dec 10 16:51:14 2021 +1100

    new format
---
 .../apache/solr/cloud/OverseerNodePrioritizer.java |   3 +-
 .../java/org/apache/solr/cloud/ZkController.java   |  18 ++-
 .../apache/solr/cloud/api/collections/Assign.java  |  17 +--
 .../java/org/apache/solr/core/CoreContainer.java   |   6 +-
 .../src/java/org/apache/solr/core/NodeRole.java    |  77 ----------
 .../src/java/org/apache/solr/core/NodeRoles.java   | 107 +++++++++++++
 .../java/org/apache/solr/handler/ClusterAPI.java   | 166 +++++++++++++--------
 .../test/org/apache/solr/cloud/NodeRolesTest.java  |  40 +++--
 .../client/solrj/cloud/DistribStateManager.java    |  23 ---
 9 files changed, 258 insertions(+), 199 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java b/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
index f88c686..4bd21dd 100644
--- a/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
+++ b/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
@@ -28,6 +28,7 @@ import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.CoreAdminParams.CoreAdminAction;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.NodeRoles;
 import org.apache.solr.handler.ClusterAPI;
 import org.apache.solr.handler.component.ShardHandler;
 import org.apache.solr.handler.component.ShardHandlerFactory;
@@ -72,7 +73,7 @@ public class OverseerNodePrioritizer {
       }
     }
 
-    overseerDesignates.addAll(ClusterAPI.getNodesByRole("overseer", new ZkDistribStateManager(zkStateReader.getZkClient())));
+    overseerDesignates.addAll(ClusterAPI.getNodesByRole(NodeRoles.Role.OVERSEER, NodeRoles.PREFERRED, new ZkDistribStateManager(zkStateReader.getZkClient())));
     if (overseerDesignates.isEmpty()) return;
     String ldr = OverseerTaskProcessor.getLeaderNode(zk);
     if(overseerDesignates.contains(ldr)) return;
diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index 05c6c5b..e7b15bf 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -73,12 +73,7 @@ import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.URLUtil;
 import org.apache.solr.common.util.Utils;
-import org.apache.solr.core.CloseHook;
-import org.apache.solr.core.CloudConfig;
-import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.CoreDescriptor;
-import org.apache.solr.core.SolrCore;
-import org.apache.solr.core.SolrCoreInitializationException;
+import org.apache.solr.core.*;
 import org.apache.solr.handler.component.HttpShardHandler;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -855,6 +850,13 @@ public class ZkController implements Closeable {
     ZkCmdExecutor cmdExecutor = new ZkCmdExecutor(zkClient.getZkClientTimeout());
     cmdExecutor.ensureExists(ZkStateReader.LIVE_NODES_ZKNODE, zkClient);
     cmdExecutor.ensureExists(ZkStateReader.NODE_ROLES, zkClient);
+    for (NodeRoles.Role role : NodeRoles.Role.values()) {
+      cmdExecutor.ensureExists(ZkStateReader.NODE_ROLES + "/" + role.roleName, zkClient);
+      for (String v : role.supportedVals()) {
+        cmdExecutor.ensureExists(ZkStateReader.NODE_ROLES + "/" + role.roleName + "/"+v, zkClient);
+      }
+    }
+
     cmdExecutor.ensureExists(ZkStateReader.COLLECTIONS_ZKNODE, zkClient);
     cmdExecutor.ensureExists(ZkStateReader.ALIASES, zkClient);
     byte[] emptyJson = "{}".getBytes(StandardCharsets.UTF_8);
@@ -1089,8 +1091,8 @@ public class ZkController implements Closeable {
     ops.add(Op.create(nodePath, null, zkClient.getZkACLProvider().getACLsToAdd(nodePath), CreateMode.EPHEMERAL));
 
     // Create the roles node as well
-    ops.add(Op.create(ZkStateReader.NODE_ROLES + "/" + nodeName,
-            Utils.toJSON(cc.nodeRoles), zkClient.getZkACLProvider().getACLsToAdd(nodePath), CreateMode.EPHEMERAL));
+   cc.nodeRoles.ROLES.forEach((role, val) -> ops.add(Op.create(ZkStateReader.NODE_ROLES + "/" + role.roleName+ "/"+val +"/"+ nodeName,
+           null, zkClient.getZkACLProvider().getACLsToAdd(nodePath), CreateMode.EPHEMERAL)));
 
     zkClient.multi(ops, true);
   }
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
index 5a74d78..185acb3 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
@@ -51,9 +51,9 @@ import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.util.StrUtils;
-import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.NodeRole;
+import org.apache.solr.core.NodeRoles;
+import org.apache.solr.handler.ClusterAPI;
 import org.apache.solr.util.NumberUtils;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
@@ -256,15 +256,10 @@ public class Assign {
 
   public static void filterNonDataNodes(DistribStateManager zk, List<String> liveNodes) {
     try {
-     zk.forEachChild(ZkStateReader.NODE_ROLES, (name, data) -> {
-       if (data != null && data.getData() != null && data.getData().length > 0) {
-         @SuppressWarnings("unchecked")
-         List<String> roles = (List<String>) Utils.fromJSON(data.getData());
-         if (!roles.contains(NodeRole.Role.DATA.toString())) {
-           liveNodes.remove(name);
-         }
-       }
-     });
+     List<String> noData =  ClusterAPI.getNodesByRole(NodeRoles.Role.DATA, NodeRoles.OFF, zk);
+     if(!noData.isEmpty()){
+       liveNodes.removeAll(noData);
+     }
     } catch (Exception e) {
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Problem fetching roles from Zookeeper", e);
     }
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 44c63db..f7e9a1f 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -246,7 +246,7 @@ public class CoreContainer {
 
   private final ObjectCache objectCache = new ObjectCache();
 
-  public final NodeRole nodeRoles = new NodeRole(System.getProperty(NodeRole.NODE_ROLES_PROP));
+  public final NodeRoles nodeRoles = new NodeRoles(System.getProperty(NodeRoles.NODE_ROLES_PROP));
 
   private final ClusterSingletons clusterSingletons = new ClusterSingletons(
       () -> getZkController() != null &&
@@ -947,9 +947,9 @@ public class CoreContainer {
       });
 
       clusterSingletons.setReady();
-      if (nodeRoles.getRoles().contains(NodeRole.Role.OVERSEER)) {
+      if (NodeRoles.PREFERRED.equals(nodeRoles.getRoleVal(NodeRoles.Role.OVERSEER))) {
         try {
-          log.info("This node is started as an overseer");
+          log.info("This node is started as a preferred overseer");
           zkSys.getZkController().setPreferredOverseer();
         } catch (KeeperException | InterruptedException e) {
           throw new SolrException(ErrorCode.SERVER_ERROR, e);
diff --git a/solr/core/src/java/org/apache/solr/core/NodeRole.java b/solr/core/src/java/org/apache/solr/core/NodeRole.java
deleted file mode 100644
index a4cf757..0000000
--- a/solr/core/src/java/org/apache/solr/core/NodeRole.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.core;
-
-import java.io.IOException;
-import java.lang.invoke.MethodHandles;
-import java.util.*;
-
-import org.apache.solr.common.IteratorWriter;
-import org.apache.solr.common.StringUtils;
-import org.apache.solr.common.util.StrUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class NodeRole implements IteratorWriter {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  public static final String NODE_ROLES_PROP = "solr.node.roles";
-
-  private final Set<Role> roles;
-
-  public NodeRole(String role) {
-    if (StringUtils.isEmpty(role)) {
-      // if no roles were specified, assume "data" role for backcompat reasons
-      roles = Set.of(Role.DATA);
-      return;
-    }
-    Set<String> rolesSet = new HashSet<>(StrUtils.split(role, ','));
-    if (rolesSet.isEmpty()) {
-      // if no roles were specified, assume "data" role for backcompat reasons
-      roles = Set.of(Role.DATA);
-      return;
-    }
-    roles = new TreeSet<>();
-    for (String r: rolesSet) {
-      roles.add(Role.valueOfCaseInsensitive(r));
-    }
-  }
-
-  public Set<Role> getRoles() {
-    return roles;
-  }
-
-  @Override
-  public void writeIter(ItemWriter iw) throws IOException {
-    for (Role role: roles) iw.add(role.toString());
-  }
-
-  public enum Role {
-    DATA, OVERSEER;
-
-    public static Role valueOfCaseInsensitive(String value) {
-      // Given a user string "overseer", convert to OVERSEER and return the enum value
-      String canonicalValue = value.toUpperCase().replace(' ', '_');
-      return Role.valueOf(canonicalValue);
-    }
-
-    @Override
-    public String toString() {
-      return super.toString().toLowerCase();
-    }
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/core/NodeRoles.java b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
new file mode 100644
index 0000000..fb3f011
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
@@ -0,0 +1,107 @@
+/*
+ * 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.core;
+
+import java.lang.invoke.MethodHandles;
+import java.util.*;
+import org.apache.solr.common.MapWriter;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.StringUtils;
+import org.apache.solr.common.util.StrUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NodeRoles implements MapWriter {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  public static final String NODE_ROLES_PROP = "solr.node.roles";
+
+  public Map<Role, String> ROLES ;
+
+  public NodeRoles(String role) {
+    ROLES = new EnumMap<>(Role.class);
+    if (StringUtils.isEmpty(role)) {
+     role = DEFAULT_ROLE;
+    }
+    List<String> rolesList = StrUtils.splitSmart(role, ',');
+    for (String s : rolesList) {
+      List<String> roleVal =  StrUtils.splitSmart(s,':');
+      Role r = Role.getRole(roleVal.get(0));
+      if(r.supportedVals().contains(roleVal.get(1))) {
+        ROLES.put(r, roleVal.get(1));
+      } else {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "UNKNOWN role value :"+roleVal.get(0));
+      }
+    }
+    ROLES = Collections.unmodifiableMap(ROLES);
+  }
+
+  public String getRoleVal(Role role) {
+    return ROLES.get(role);
+  }
+
+
+
+  @Override
+  public void writeMap(EntryWriter ew) {
+    ROLES.forEach((role, s) -> ew.putNoEx(role.roleName, s));
+  }
+
+  public enum Role {
+    DATA("data") ,
+    OVERSEER("overseer") {
+      @Override
+      public Set<String> supportedVals() {
+        return OVERSEER_VALS;
+      }
+    },
+    COORDINATOR("coordinator");
+
+    public final String roleName;
+
+    Role(String name) {
+      this.roleName = name;
+    }
+
+    public static Role getRole(String value) {
+      // Given a user string "overseer", convert to OVERSEER and return the enum value
+      for (Role role : Role.values()) {
+        if(value.equals(role.roleName)) return role;
+      }
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown role : "+value);
+    }
+
+    public Set<String> supportedVals() {
+      return ON_OFF;
+
+    }
+
+    @Override
+    public String toString() {
+      return super.toString().toLowerCase();
+    }
+  }
+
+  public static final String ON =  "on";
+  public static final String OFF =  "off";
+  public static final Set<String> ON_OFF = Set.of(ON,OFF);
+  public static final String ALLOWED =  "allowed";
+  public static final String DISALLOWED =  "disallowed";
+  public static final String PREFERRED =  "preferred";
+  public static final Set<String> OVERSEER_VALS = Set.of(ALLOWED, DISALLOWED, PREFERRED);
+  public static final String DEFAULT_ROLE = "data:on,overseer:allowed";
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index 5f512d2..796ac49 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -42,6 +42,7 @@ import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.ReflectMapWriter;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.NodeRoles;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.handler.admin.ConfigSetsHandler;
 import org.apache.solr.request.SolrQueryRequest;
@@ -63,20 +64,21 @@ import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PER
 import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_EDIT_PERM;
 import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM;
 
-/** All V2 APIs that have  a prefix of /api/cluster/
- *
+/**
+ * All V2 APIs that have  a prefix of /api/cluster/
  */
 public class ClusterAPI {
   private final CollectionsHandler collectionsHandler;
   private final ConfigSetsHandler configSetsHandler;
 
-  public  final Commands commands = new Commands();
-  public  final ConfigSetCommands configSetCommands = new ConfigSetCommands();
+  public final Commands commands = new Commands();
+  public final ConfigSetCommands configSetCommands = new ConfigSetCommands();
 
   public ClusterAPI(CollectionsHandler ch, ConfigSetsHandler configSetsHandler) {
     this.collectionsHandler = ch;
     this.configSetsHandler = configSetsHandler;
   }
+
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   @EndPoint(method = GET,
@@ -85,14 +87,45 @@ public class ClusterAPI {
   @SuppressWarnings("unchecked")
   // nocommit: it must also output all data nodes that didn't start with -Dsolr.node.roles
   public void roles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
-    Map <String, List<String>> result = new LinkedHashMap<>();
-    collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager().
-            forEachChild(ZkStateReader.NODE_ROLES, (node, data) -> {
-      if (data != null && data.getData() != null) {
-        result.put(node, ((List<String>) Utils.fromJSON(data.getData())));
+    Map<String, Object> result = new LinkedHashMap<>();
+
+    rsp.add("node-roles", readRecursive(ZkStateReader.NODE_ROLES, collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager(), 3));
+  }
+
+  Object readRecursive(String path, DistribStateManager zk, int depth) {
+    if (depth == 0) return null;
+    Map<String, Object> result = null;
+    boolean hasSubValues = false;
+    try {
+      List<String> children = zk.listData(path);
+      if (children != null && !children.isEmpty()) {
+        result = new HashMap<>();
+      } else {
+        return depth >=1 ? Collections.emptyList(): null;
       }
-    });
-    rsp.add("node-roles", result);
+      for (String child : children) {
+        Object c = readRecursive(path + "/" + child, zk, depth - 1);
+        if (c != null) {
+          hasSubValues = true;
+        }
+        result.put(child, c);
+      }
+
+
+    } catch (Exception e) {
+    }
+
+
+
+    if (result == null) {
+      return null;
+    }
+    if (depth == 1) {
+      return result.keySet();
+    } else {
+      return result;
+    }
+
   }
 
   @EndPoint(method = GET,
@@ -100,28 +133,35 @@ public class ClusterAPI {
           permission = COLL_READ_PERM)
   public void nodesWithRole(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     String role = req.getPathTemplateValues().get("role");
-    rsp.add("nodes",
-            getNodesByRole(role, collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager()));
+    rsp.add(role,
+            readRecursive(ZkStateReader.NODE_ROLES + "/"+ role,
+                    collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager(), 2));
+  }
+  @EndPoint(method = GET,
+          path = "/cluster/node-roles/{role}/{role-val}",
+          permission = COLL_READ_PERM)
+  public void nodesWithRoleVal(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+    String role = req.getPathTemplateValues().get("role");
+    String roleVal = req.getPathTemplateValues().get("role-val");
+    List<String> nodes =  collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager()
+            .getDistribStateManager().listData(ZkStateReader.NODE_ROLES + "/"+ role+"/"+roleVal);
+    rsp.add( role, Collections.singletonMap(roleVal, nodes));
   }
 
-  public static List<String> getNodesByRole(String role, DistribStateManager zk) throws InterruptedException, IOException, KeeperException {
-    List<String> result = new ArrayList<>();
-    zk.forEachChild(ZkStateReader.NODE_ROLES, (node, data) -> {
-      if (data != null && data.getData() != null) {
-        @SuppressWarnings("unchecked")
-        List<String> rolesData = (List<String>) Utils.fromJSON(data.getData());
-        if (rolesData.contains(role)) {
-          result.add(node);
-        }
-      }
-    });
-    return result;
+   public static List<String> getNodesByRole(NodeRoles.Role role, String val, DistribStateManager zk)
+          throws InterruptedException, IOException, KeeperException {
+    try {
+      return zk.listData(ZkStateReader.NODE_ROLES+"/"+ role+ "/"+val);
+    } catch (NoSuchElementException e) {
+      return Collections.emptyList();
+    }
+
   }
 
 
   @EndPoint(method = GET,
-      path = "/cluster/aliases",
-      permission = COLL_READ_PERM)
+          path = "/cluster/aliases",
+          permission = COLL_READ_PERM)
   public void aliases(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     final Map<String, Object> v1Params = Maps.newHashMap();
     v1Params.put(ACTION, CollectionParams.CollectionAction.LISTALIASES.lowerName);
@@ -129,58 +169,58 @@ public class ClusterAPI {
   }
 
   @EndPoint(method = GET,
-      path = "/cluster/overseer",
-      permission = COLL_READ_PERM)
+          path = "/cluster/overseer",
+          permission = COLL_READ_PERM)
   public void getOverseerStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     collectionsHandler.handleRequestBody(wrapParams(req, "action", OVERSEERSTATUS.lowerName), rsp);
   }
 
   @EndPoint(method = GET,
-      path = "/cluster",
-      permission = COLL_READ_PERM)
+          path = "/cluster",
+          permission = COLL_READ_PERM)
   public void getCluster(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     collectionsHandler.handleRequestBody(wrapParams(req, "action", LIST.lowerName), rsp);
   }
 
   @EndPoint(method = DELETE,
-      path = "/cluster/command-status/{id}",
-      permission = COLL_EDIT_PERM)
+          path = "/cluster/command-status/{id}",
+          permission = COLL_EDIT_PERM)
   public void deleteCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     final Map<String, Object> v1Params = Maps.newHashMap();
     v1Params.put(ACTION, DELETESTATUS.lowerName);
     v1Params.put(REQUESTID, req.getPathTemplateValues().get("id"));
     collectionsHandler.handleRequestBody(wrapParams(req, v1Params), rsp);
   }
-  
+
   @EndPoint(method = DELETE,
-      path = "/cluster/command-status",
-      permission = COLL_EDIT_PERM)
+          path = "/cluster/command-status",
+          permission = COLL_EDIT_PERM)
   public void flushCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     CollectionsHandler.CollectionOperation.DELETESTATUS_OP.execute(req, rsp, collectionsHandler);
   }
 
   @EndPoint(method = DELETE,
-      path =   "/cluster/configs/{name}",
-      permission = CONFIG_EDIT_PERM
+          path = "/cluster/configs/{name}",
+          permission = CONFIG_EDIT_PERM
   )
   public void deleteConfigSet(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     req = wrapParams(req,
-        "action", ConfigSetParams.ConfigSetAction.DELETE.toString(),
-        CommonParams.NAME, req.getPathTemplateValues().get("name"));
+            "action", ConfigSetParams.ConfigSetAction.DELETE.toString(),
+            CommonParams.NAME, req.getPathTemplateValues().get("name"));
     configSetsHandler.handleRequestBody(req, rsp);
   }
 
   @EndPoint(method = GET,
-      path = "/cluster/configs",
-      permission = CONFIG_READ_PERM)
+          path = "/cluster/configs",
+          permission = CONFIG_READ_PERM)
   public void listConfigSet(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     req = wrapParams(req, "action", ConfigSetParams.ConfigSetAction.LIST.toString());
     configSetsHandler.handleRequestBody(req, rsp);
   }
 
   @EndPoint(method = POST,
-      path = "/cluster/configs",
-      permission = CONFIG_EDIT_PERM
+          path = "/cluster/configs",
+          permission = CONFIG_EDIT_PERM
   )
   public class ConfigSetCommands {
 
@@ -188,8 +228,8 @@ public class ClusterAPI {
     @SuppressWarnings("unchecked")
     public void create(PayloadObj<CreateConfigPayload> obj) throws Exception {
       Map<String, Object> mapVals = obj.get().toMap(new HashMap<>());
-      Map<String,Object> customProps = obj.get().properties;
-      if(customProps!= null) {
+      Map<String, Object> customProps = obj.get().properties;
+      if (customProps != null) {
         customProps.forEach((k, o) -> mapVals.put(ConfigSetCmds.CONFIG_SET_PROPERTY_PREFIX + k, o));
       }
       mapVals.put("action", ConfigSetParams.ConfigSetAction.CREATE.toString());
@@ -199,8 +239,8 @@ public class ClusterAPI {
   }
 
   @EndPoint(method = PUT,
-      path =   "/cluster/configs/{name}",
-      permission = CONFIG_EDIT_PERM
+          path = "/cluster/configs/{name}",
+          permission = CONFIG_EDIT_PERM
   )
   public void uploadConfigSet(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     req = wrapParams(req,
@@ -212,8 +252,8 @@ public class ClusterAPI {
   }
 
   @EndPoint(method = PUT,
-      path =   "/cluster/configs/{name}/*",
-      permission = CONFIG_EDIT_PERM
+          path = "/cluster/configs/{name}/*",
+          permission = CONFIG_EDIT_PERM
   )
   public void insertIntoConfigSet(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     String path = req.getPathTemplateValues().get("*");
@@ -236,17 +276,17 @@ public class ClusterAPI {
   public static SolrQueryRequest wrapParams(SolrQueryRequest req, Map<String, Object> m) {
     ModifiableSolrParams solrParams = new ModifiableSolrParams();
     m.forEach((k, v) -> {
-      if(v == null) return;
+      if (v == null) return;
       solrParams.add(k.toString(), String.valueOf(v));
     });
-    DefaultSolrParams dsp = new DefaultSolrParams(req.getParams(),solrParams);
+    DefaultSolrParams dsp = new DefaultSolrParams(req.getParams(), solrParams);
     req.setParams(dsp);
     return req;
   }
 
   @EndPoint(method = GET,
-      path = "/cluster/command-status/{id}",
-      permission = COLL_READ_PERM)
+          path = "/cluster/command-status/{id}",
+          permission = COLL_READ_PERM)
   public void getCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     final Map<String, Object> v1Params = Maps.newHashMap();
     v1Params.put(ACTION, REQUESTSTATUS.lowerName);
@@ -255,8 +295,8 @@ public class ClusterAPI {
   }
 
   @EndPoint(method = GET,
-      path = "/cluster/nodes",
-      permission = COLL_READ_PERM)
+          path = "/cluster/nodes",
+          permission = COLL_READ_PERM)
   public void getNodes(SolrQueryRequest req, SolrQueryResponse rsp) {
     rsp.add("nodes", getCoreContainer().getZkController().getClusterState().getLiveNodes());
   }
@@ -266,13 +306,13 @@ public class ClusterAPI {
   }
 
   @EndPoint(method = POST,
-      path = "/cluster",
-      permission = COLL_EDIT_PERM)
+          path = "/cluster",
+          permission = COLL_EDIT_PERM)
   public class Commands {
     @Command(name = "add-role")
     public void addRole(PayloadObj<RoleInfo> obj) throws Exception {
       RoleInfo info = obj.get();
-      Map<String,Object> m = info.toMap(new HashMap<>());
+      Map<String, Object> m = info.toMap(new HashMap<>());
       m.put("action", ADDROLE.toString());
       collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), m), obj.getResponse());
     }
@@ -280,7 +320,7 @@ public class ClusterAPI {
     @Command(name = "remove-role")
     public void removeRole(PayloadObj<RoleInfo> obj) throws Exception {
       RoleInfo info = obj.get();
-      Map<String,Object> m = info.toMap(new HashMap<>());
+      Map<String, Object> m = info.toMap(new HashMap<>());
       m.put("action", REMOVEROLE.toString());
       collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), m), obj.getResponse());
     }
@@ -288,7 +328,7 @@ public class ClusterAPI {
     @Command(name = "set-obj-property")
     public void setObjProperty(PayloadObj<ClusterPropPayload> obj) {
       //Not using the object directly here because the API differentiate between {name:null} and {}
-      Map<String,Object> m = obj.getDataMap();
+      Map<String, Object> m = obj.getDataMap();
       ClusterProperties clusterProperties = new ClusterProperties(getCoreContainer().getZkController().getZkClient());
       try {
         clusterProperties.setClusterProperties(m);
@@ -298,8 +338,8 @@ public class ClusterAPI {
     }
 
     @Command(name = "set-property")
-    public void setProperty(PayloadObj<Map<String,String>> obj) throws Exception {
-      Map<String,Object> m =  obj.getDataMap();
+    public void setProperty(PayloadObj<Map<String, String>> obj) throws Exception {
+      Map<String, Object> m = obj.getDataMap();
       m.put("action", CLUSTERPROP.toString());
       collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), m), obj.getResponse());
     }
diff --git a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
index e5f2ee1..18a16fe 100644
--- a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
@@ -18,14 +18,14 @@
 package org.apache.solr.cloud;
 
 import java.lang.invoke.MethodHandles;
-import java.util.List;
-import java.util.TreeSet;
+import java.util.Collection;
+import java.util.Collections;
 
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.V2Request;
 import org.apache.solr.client.solrj.response.V2Response;
-import org.apache.solr.core.NodeRole;
+import org.apache.solr.core.NodeRoles;
 import org.junit.After;
 import org.junit.Before;
 import org.slf4j.Logger;
@@ -51,20 +51,34 @@ public class NodeRolesTest extends SolrCloudTestCase {
 
     JettySolrRunner j0 = cluster.getJettySolrRunner(0);
     JettySolrRunner j1 = null, j2 = null;
-    j1 = startNodeWithRoles("overseer");
+    j1 = startNodeWithRoles("overseer:preferred,data:off");
 
     V2Response rsp = new V2Request.Builder("/cluster/node-roles").GET().build().process(cluster.getSolrClient());
-    assertEquals(List.of("overseer"), rsp._get(List.of("node-roles", j1.getNodeName()), null));
-
+    assertEquals(j1.getNodeName(), rsp._getStr("node-roles/overseer/preferred[0]", null));
+    assertEquals(j1.getNodeName(), rsp._getStr("node-roles/data/off[0]", null));
     OverseerRolesTest.waitForNewOverseer(20, j1.getNodeName(), false);
 
     //start another node that is overseer but has data
-    j2 = startNodeWithRoles("overseer,data");
+    j2 = startNodeWithRoles("overseer:preferred,data:on");
     rsp = new V2Request.Builder("/cluster/node-roles").GET().build().process(cluster.getSolrClient());
-    assertEquals(List.of("data", "overseer"), rsp._get(List.of("node-roles", j2.getNodeName()), null));
+
+
+    assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
+    assertTrue( ((Collection)rsp._get("node-roles/data/on", Collections.emptyList())).contains(j2.getNodeName()));
+
+
 
     rsp = new V2Request.Builder("/cluster/node-roles/overseer").GET().build().process(cluster.getSolrClient());
-    assertEquals(new TreeSet<String>(List.of(j1.getNodeName(), j2.getNodeName())), new TreeSet<String>((List<String>) rsp._get("nodes", null)));
+
+
+    assertTrue( ((Collection)rsp._get("overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
+    assertTrue( ((Collection)rsp._get("overseer/preferred", Collections.emptyList())).contains(j1.getNodeName()));
+
+    rsp = new V2Request.Builder("/cluster/node-roles/overseer/preferred").GET().build().process(cluster.getSolrClient());
+    assertTrue( ((Collection)rsp._get("overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
+    assertTrue( ((Collection)rsp._get("overseer/preferred", Collections.emptyList())).contains(j1.getNodeName()));
+
+
 
     String COLLECTION_NAME = "TEST_ROLES";
     CollectionAdminRequest
@@ -79,17 +93,17 @@ public class NodeRolesTest extends SolrCloudTestCase {
 
     // Shutdown the dedicated overseer, make sure that node disappears from the roles output
     j1.stop();
-    rsp = new V2Request.Builder("/cluster/node-roles").GET().build().process(cluster.getSolrClient());
-    assertNull(rsp._get(List.of("node-roles", dedicatedOverseer), null));
+    rsp = new V2Request.Builder("/cluster/node-roles/overseer/preferred").GET().build().process(cluster.getSolrClient());
+    assertFalse (((Collection) rsp._get("overseer/preferred" , null)). contains(j1.getNodeName()));
   }
 
   private JettySolrRunner startNodeWithRoles(String roles) throws Exception {
     JettySolrRunner jetty;
-    System.setProperty(NodeRole.NODE_ROLES_PROP, roles);
+    System.setProperty(NodeRoles.NODE_ROLES_PROP, roles);
     try {
       jetty = cluster.startJettySolrRunner();
     } finally {
-      System.clearProperty(NodeRole.NODE_ROLES_PROP);
+      System.clearProperty(NodeRoles.NODE_ROLES_PROP);
     }
     return jetty;
   }
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/DistribStateManager.java b/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/DistribStateManager.java
index 84010c8..feae27f 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/DistribStateManager.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/DistribStateManager.java
@@ -24,10 +24,8 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.NoSuchElementException;
 
-import java.util.function.BiConsumer;
 import org.apache.solr.common.SolrCloseable;
 import org.apache.solr.common.cloud.PerReplicaStates;
-import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.Op;
@@ -143,25 +141,4 @@ public interface DistribStateManager extends SolrCloseable {
       }
     }
   }
-  default void forEachChild(String path, BiConsumer<String, VersionedData> fun)
-          throws InterruptedException, IOException, KeeperException {
-    List<String> children = null;
-    try {
-      children = listData(path);
-    } catch (NoSuchElementException e) {
-      return;
-    }
-    if (!children.isEmpty()) {
-      for (String node: children) {
-        VersionedData data = null;
-        try {
-          data = getData(path + "/" + node);
-        } catch (NoSuchElementException e) {
-          // this node must've been deleted
-          continue;
-        }
-        fun.accept(node, data);
-      }
-    }
-  }
 }

[solr] 16/28: clanup

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit c941fd8e40c55e579ba34cbf422bd60710a77514
Author: Noble Paul <no...@gmail.com>
AuthorDate: Mon Dec 13 14:35:30 2021 +1100

    clanup
---
 solr/core/src/java/org/apache/solr/core/NodeRoles.java | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/solr/core/src/java/org/apache/solr/core/NodeRoles.java b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
index 4bb04e0..69c2572 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeRoles.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
@@ -36,6 +36,7 @@ public class NodeRoles implements MapWriter {
   public static final String DISALLOWED =  "disallowed";
   public static final String PREFERRED =  "preferred";
   public static final Set<String> OVERSEER_MODES = Set.of(ALLOWED, PREFERRED, DISALLOWED);
+  public static final Set<String> ON_OFF = Set.of(ON,OFF);
 
   public static final String DEFAULT_ROLES_STRING = "data:on,overseer:allowed";
 
@@ -110,7 +111,7 @@ public class NodeRoles implements MapWriter {
     }
 
     public Set<String> supportedModes() {
-      return Set.of(ON, OFF);
+      return ON_OFF;
     }
 
     /**

[solr] 02/28: license header

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 277e4156cbb067f20ef6f588fdb6aa3b78d352d3
Author: Noble Paul <no...@gmail.com>
AuthorDate: Wed Nov 10 00:12:44 2021 +1100

    license header
---
 .../core/src/test/org/apache/solr/core/TestNodeRole.java | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/solr/core/src/test/org/apache/solr/core/TestNodeRole.java b/solr/core/src/test/org/apache/solr/core/TestNodeRole.java
index d5bdba5..b328983 100644
--- a/solr/core/src/test/org/apache/solr/core/TestNodeRole.java
+++ b/solr/core/src/test/org/apache/solr/core/TestNodeRole.java
@@ -1,3 +1,19 @@
+/*
+ * 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.core;
 
 import java.lang.invoke.MethodHandles;

[solr] 22/28: Fixing precommit

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 3c17462d000b47c3ffd79cc60021f6eb030dfc9f
Author: Ishan Chattopadhyaya <is...@apache.org>
AuthorDate: Fri Dec 17 14:30:16 2021 +0530

    Fixing precommit
---
 solr/core/src/java/org/apache/solr/core/NodeRoles.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/solr/core/src/java/org/apache/solr/core/NodeRoles.java b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
index 69c2572..71c0303 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeRoles.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
@@ -123,7 +123,7 @@ public class NodeRoles implements MapWriter {
 
     @Override
     public String toString() {
-      return super.toString().toLowerCase();
+      return super.toString().toLowerCase(Locale.ROOT);
     }
   }
 }

[solr] 26/28: Addressing review feedback

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 50f40a1cbf98c049eb6fd9a89c814770a8de4513
Author: Ishan Chattopadhyaya <is...@apache.org>
AuthorDate: Wed Dec 22 07:15:00 2021 +0530

    Addressing review feedback
---
 .../apache/solr/cloud/OverseerNodePrioritizer.java | 10 ++-
 .../java/org/apache/solr/cloud/ZkController.java   | 14 ++--
 .../apache/solr/cloud/api/collections/Assign.java  |  4 +-
 .../java/org/apache/solr/core/CoreContainer.java   |  4 +-
 .../src/java/org/apache/solr/core/NodeRoles.java   | 93 ++++++++++++++--------
 .../java/org/apache/solr/handler/ClusterAPI.java   | 40 +++++-----
 .../test/org/apache/solr/cloud/NodeRolesTest.java  |  2 +-
 solr/solr-ref-guide/src/node-roles.adoc            |  2 +-
 .../apache/solr/common/cloud/ZkStateReader.java    | 10 ++-
 9 files changed, 110 insertions(+), 69 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java b/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
index 4bd21dd..db162a1 100644
--- a/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
+++ b/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
@@ -73,7 +73,15 @@ public class OverseerNodePrioritizer {
       }
     }
 
-    overseerDesignates.addAll(ClusterAPI.getNodesByRole(NodeRoles.Role.OVERSEER, NodeRoles.PREFERRED, new ZkDistribStateManager(zkStateReader.getZkClient())));
+    List<String> preferredOverseers = ClusterAPI.getNodesByRole(NodeRoles.Role.OVERSEER, NodeRoles.Mode.PREFERRED,
+            new ZkDistribStateManager(zkStateReader.getZkClient()));
+    for (String preferred: preferredOverseers) {
+      if (overseerDesignates.contains(preferred)) {
+        log.warn("Node " + preferred + " has been configured to be a preferred overseer using both ADDROLE API command " +
+                "as well as using Node Roles (i.e. -Dsolr.node.roles start up property). Only the latter is recommended.");
+      }
+    }
+    overseerDesignates.addAll(preferredOverseers);
     if (overseerDesignates.isEmpty()) return;
     String ldr = OverseerTaskProcessor.getLeaderNode(zk);
     if(overseerDesignates.contains(ldr)) return;
diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index ee0c87f..8688bfd 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -358,7 +358,7 @@ public class ZkController implements Closeable {
 
                 overseerElector.setup(context);
 
-                if(cc.nodeRoles.isOverseerAllowed()) {
+                if (cc.nodeRoles.isOverseerAllowedOrPreferred()) {
                   overseerElector.joinElection(context, true);
                 }
               }
@@ -858,10 +858,10 @@ public class ZkController implements Closeable {
     ZkCmdExecutor cmdExecutor = new ZkCmdExecutor(zkClient.getZkClientTimeout());
     cmdExecutor.ensureExists(ZkStateReader.LIVE_NODES_ZKNODE, zkClient);
     cmdExecutor.ensureExists(ZkStateReader.NODE_ROLES, zkClient);
-    for (NodeRoles.Role role : NodeRoles.Role.values()) {
-      cmdExecutor.ensureExists(ZkStateReader.NODE_ROLES + "/" + role.roleName, zkClient);
-      for (String v : role.supportedModes()) {
-        cmdExecutor.ensureExists(ZkStateReader.NODE_ROLES + "/" + role.roleName + "/"+v, zkClient);
+    for (NodeRoles.Role role: NodeRoles.Role.values()) {
+      cmdExecutor.ensureExists(NodeRoles.getZNodeForRole(role), zkClient);
+      for (NodeRoles.Mode mode: role.supportedModes()) {
+        cmdExecutor.ensureExists(NodeRoles.getZNodeForRoleMode(role, mode), zkClient);
       }
     }
 
@@ -924,7 +924,7 @@ public class ZkController implements Closeable {
         ElectionContext context = new OverseerElectionContext(zkClient,
             overseer, getNodeName());
         overseerElector.setup(context);
-        if(cc.nodeRoles.isOverseerAllowed()) {
+        if (cc.nodeRoles.isOverseerAllowedOrPreferred()) {
           overseerElector.joinElection(context, false);
         }
       }
@@ -1101,7 +1101,7 @@ public class ZkController implements Closeable {
     ops.add(Op.create(nodePath, null, zkClient.getZkACLProvider().getACLsToAdd(nodePath), CreateMode.EPHEMERAL));
 
     // Create the roles node as well
-   cc.nodeRoles.getRoles().forEach((role, val) -> ops.add(Op.create(ZkStateReader.NODE_ROLES + "/" + role.roleName+ "/"+val +"/"+ nodeName,
+   cc.nodeRoles.getRoles().forEach((role, mode) -> ops.add(Op.create(NodeRoles.getZNodeForRoleMode(role, mode) +"/" + nodeName,
            null, zkClient.getZkACLProvider().getACLsToAdd(nodePath), CreateMode.EPHEMERAL)));
 
     zkClient.multi(ops, true);
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
index 185acb3..7dfcc33 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
@@ -256,8 +256,8 @@ public class Assign {
 
   public static void filterNonDataNodes(DistribStateManager zk, List<String> liveNodes) {
     try {
-     List<String> noData =  ClusterAPI.getNodesByRole(NodeRoles.Role.DATA, NodeRoles.OFF, zk);
-     if(!noData.isEmpty()){
+     List<String> noData =  ClusterAPI.getNodesByRole(NodeRoles.Role.DATA, NodeRoles.Mode.OFF, zk);
+     if (!noData.isEmpty()) {
        liveNodes.removeAll(noData);
      }
     } catch (Exception e) {
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 72a0df3..e272767 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -947,9 +947,9 @@ public class CoreContainer {
       });
 
       clusterSingletons.setReady();
-      if (NodeRoles.PREFERRED.equals(nodeRoles.getRoleMode(NodeRoles.Role.OVERSEER))) {
+      if (NodeRoles.Mode.PREFERRED.equals(nodeRoles.getRoleMode(NodeRoles.Role.OVERSEER))) {
         try {
-          log.info("This node is started as a preferred overseer");
+          log.info("This node has been started as a preferred overseer");
           zkSys.getZkController().setPreferredOverseer();
         } catch (KeeperException | InterruptedException e) {
           throw new SolrException(ErrorCode.SERVER_ERROR, e);
diff --git a/solr/core/src/java/org/apache/solr/core/NodeRoles.java b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
index 71c0303..714d8b1 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeRoles.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
@@ -21,30 +21,29 @@ import java.util.*;
 import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.StringUtils;
+import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.util.StrUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class NodeRoles implements MapWriter {
+public class NodeRoles {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   public static final String NODE_ROLES_PROP = "solr.node.roles";
 
-  public static final String ON =  "on";
+  /*public static final String ON =  "on";
   public static final String OFF =  "off";
   public static final String ALLOWED =  "allowed";
   public static final String DISALLOWED =  "disallowed";
-  public static final String PREFERRED =  "preferred";
-  public static final Set<String> OVERSEER_MODES = Set.of(ALLOWED, PREFERRED, DISALLOWED);
-  public static final Set<String> ON_OFF = Set.of(ON,OFF);
+  public static final String PREFERRED =  "preferred";*/
 
   public static final String DEFAULT_ROLES_STRING = "data:on,overseer:allowed";
 
   // Map of roles to mode that are applicable for this node.
-  private Map<Role, String> nodeRoles;
+  private Map<Role, Mode> nodeRoles;
 
   public NodeRoles(String rolesString) {
-    Map<Role, String> roles = new EnumMap<>(Role.class);
+    Map<Role, Mode> roles = new EnumMap<>(Role.class);
     if (StringUtils.isEmpty(rolesString)) {
      rolesString = DEFAULT_ROLES_STRING;
     }
@@ -52,10 +51,12 @@ public class NodeRoles implements MapWriter {
     for (String s: rolesList) {
       List<String> roleMode =  StrUtils.splitSmart(s,':');
       Role r = Role.getRole(roleMode.get(0));
-      if (r.supportedModes().contains(roleMode.get(1))) {
-        roles.put(r, roleMode.get(1));
+      Mode m = Mode.valueOf(roleMode.get(1).toUpperCase(Locale.ROOT));
+      if (r.supportedModes().contains(m)) {
+        roles.put(r, m);
       } else {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown role mode: " + roleMode.get(0));
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+                "Unknown role mode '" + roleMode.get(1) + "' for role '" + r + "'");
       }
     }
     for(Role r: Role.values()) {
@@ -66,34 +67,51 @@ public class NodeRoles implements MapWriter {
     nodeRoles = Collections.unmodifiableMap(roles);
   }
 
-  public Map<Role, String> getRoles() {
+  public Map<Role, Mode> getRoles() {
     return nodeRoles;
   }
 
-  public String getRoleMode(Role role) {
+  public Mode getRoleMode(Role role) {
     return nodeRoles.get(role);
   }
 
-  @Override
-  public void writeMap(EntryWriter ew) {
-    nodeRoles.forEach((role, s) -> ew.putNoEx(role.roleName, s));
+  public boolean isOverseerAllowedOrPreferred() {
+    Mode roleMode = nodeRoles.get(Role.OVERSEER);
+    return Mode.ALLOWED.equals(roleMode) || Mode.PREFERRED.equals(roleMode);
   }
 
-  public boolean isOverseerAllowed() {
-    String roleMode = nodeRoles.get(Role.OVERSEER);
-    return ALLOWED.equals(roleMode) || PREFERRED.equals(roleMode);
-  }
+  public enum Mode {
+    ON, OFF, ALLOWED, PREFERRED, DISALLOWED;
+
+    @Override
+    /**
+     * Need this lowercasing so that the ZK references use the lowercase form, which is
+     * also the form documented in user facing documentation.
+     */
+    public String toString() {
+      return name().toLowerCase(Locale.ROOT);
+    }
+  };
 
   public enum Role {
-    DATA("data"),
+    DATA("data") {
+      @Override
+      public Set<Mode> supportedModes() {
+        return Set.of(Mode.ON, Mode.OFF);
+      }
+      @Override
+      public Mode defaultIfAbsent() {
+        return Mode.OFF;
+      }
+    },
     OVERSEER("overseer") {
       @Override
-      public Set<String> supportedModes() {
-        return OVERSEER_MODES;
+      public Set<Mode> supportedModes() {
+        return Set.of(Mode.ALLOWED, Mode.PREFERRED, Mode.DISALLOWED);
       }
       @Override
-      public String defaultIfAbsent() {
-        return DISALLOWED;
+      public Mode defaultIfAbsent() {
+        return Mode.DISALLOWED;
       }
     };
 
@@ -104,26 +122,33 @@ public class NodeRoles implements MapWriter {
     }
 
     public static Role getRole(String value) {
-      for (Role role: Role.values()) {
-        if (value.equals(role.roleName)) return role;
+      try {
+        Role role = Role.valueOf(value.toUpperCase(Locale.ROOT));
+        return role;
+      } catch (IllegalArgumentException ex) {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown role: " + value);
       }
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown role: " + value);
     }
 
-    public Set<String> supportedModes() {
-      return ON_OFF;
-    }
+    public abstract Set<Mode> supportedModes();
 
     /**
      * Default mode for a role in nodes where this role is not specified.
      */
-    public String defaultIfAbsent() {
-      return OFF;
-    }
+    public abstract Mode defaultIfAbsent();
 
     @Override
     public String toString() {
-      return super.toString().toLowerCase(Locale.ROOT);
+      return roleName;
     }
   }
+
+  public static String getZNodeForRole(Role role) {
+    return ZkStateReader.NODE_ROLES + "/" + role.roleName;
+  }
+
+  public static String getZNodeForRoleMode(Role role, Mode mode) {
+    return ZkStateReader.NODE_ROLES + "/" + role.roleName + "/" + mode;
+  }
+
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index 6a0148e..dce5ea5 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -20,6 +20,7 @@ package org.apache.solr.handler;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.*;
+import java.util.stream.Collectors;
 
 import com.google.common.collect.Maps;
 import org.apache.solr.api.Command;
@@ -85,23 +86,21 @@ public class ClusterAPI {
           path = "/cluster/node-roles",
           permission = COLL_READ_PERM)
   public void roles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
-    Map<String, Object> result = new LinkedHashMap<>();
-
     rsp.add("node-roles", readRecursive(ZkStateReader.NODE_ROLES,
             collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager(), 3));
   }
 
   Object readRecursive(String path, DistribStateManager zk, int depth) {
     if (depth == 0) return null;
-    Map<String, Object> result = null;
+    Map<String, Object> result;
     try {
       List<String> children = zk.listData(path);
       if (children != null && !children.isEmpty()) {
         result = new HashMap<>();
       } else {
-        return depth >= 1 ? Collections.emptyList() : null;
+        return depth >= 1 ? Collections.emptySet(): null;
       }
-      for (String child : children) {
+      for (String child: children) {
         Object c = readRecursive(path + "/" + child, zk, depth - 1);
         result.put(child, c);
       }
@@ -121,7 +120,7 @@ public class ClusterAPI {
   public void nodesWithRole(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     String role = req.getPathTemplateValues().get("role");
     rsp.add("node-roles", Map.of(role,
-            readRecursive(ZkStateReader.NODE_ROLES + "/"+ role,
+            readRecursive(ZkStateReader.NODE_ROLES + "/" + role,
                     collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager(), 2)));
   }
 
@@ -135,10 +134,8 @@ public class ClusterAPI {
     Map<String, Map<String, Set<String>>> roles = (Map<String, Map<String, Set<String>>>) readRecursive(ZkStateReader.NODE_ROLES,
             collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager(), 3);
     for (String role: roles.keySet()) {
-      for (String mode : roles.get(role).keySet()) {
-        if (roles.get(role).get(mode) instanceof List && ((List) roles.get(role).get(mode)).size() == 0) {
-          continue;
-        }
+      for (String mode: roles.get(role).keySet()) {
+        if (roles.get(role).get(mode).isEmpty()) continue;
         Set<String> nodes = roles.get(role).get(mode);
         if (nodes.contains(node)) ret.put(role, mode);
       }
@@ -154,30 +151,33 @@ public class ClusterAPI {
   public void supportedRoles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
      Map<String, Object> roleModesSupportedMap = new HashMap<>();
     for (NodeRoles.Role role: NodeRoles.Role.values()) {
-      roleModesSupportedMap.put(role.toString(), Map.of("modes", role.supportedModes(), "defaultIfAbsent", role.defaultIfAbsent()));
+      roleModesSupportedMap.put(role.toString(),
+              Map.of("modes", (role.supportedModes()).stream().map(NodeRoles.Mode::toString).collect(Collectors.toList())));
     }
     rsp.add("supported-roles", roleModesSupportedMap);
   }
 
   @EndPoint(method = GET,
-          path = "/cluster/node-roles/role/{role}/{role-val}",
+          path = "/cluster/node-roles/role/{role}/{mode}",
           permission = COLL_READ_PERM)
-  public void nodesWithRoleVal(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
-    String role = req.getPathTemplateValues().get("role");
-    String roleVal = req.getPathTemplateValues().get("role-val");
+  public void nodesWithRoleMode(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+    // Here, deal with raw strings instead of Role & Mode types so as to handle roles and modes
+    // that are not understood by this node (possibly during a rolling upgrade)
+    String roleStr = req.getPathTemplateValues().get("role");
+    String modeStr = req.getPathTemplateValues().get("mode");
+
     List<String> nodes =  collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager()
-            .getDistribStateManager().listData(ZkStateReader.NODE_ROLES + "/"+ role+"/"+roleVal);
-    rsp.add( "node-roles", Map.of(role, Collections.singletonMap(roleVal, nodes)));
+            .getDistribStateManager().listData(ZkStateReader.NODE_ROLES + "/" + roleStr + "/" + modeStr);
+    rsp.add( "node-roles", Map.of(roleStr, Collections.singletonMap(modeStr, nodes)));
   }
 
-   public static List<String> getNodesByRole(NodeRoles.Role role, String val, DistribStateManager zk)
+   public static List<String> getNodesByRole(NodeRoles.Role role, NodeRoles.Mode mode, DistribStateManager zk)
           throws InterruptedException, IOException, KeeperException {
     try {
-      return zk.listData(ZkStateReader.NODE_ROLES+"/"+ role+ "/"+val);
+      return zk.listData(ZkStateReader.NODE_ROLES + "/" + role + "/" + mode);
     } catch (NoSuchElementException e) {
       return Collections.emptyList();
     }
-
   }
 
 
diff --git a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
index 63f7d86..0fc0e95 100644
--- a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
@@ -52,7 +52,7 @@ public class NodeRolesTest extends SolrCloudTestCase {
   @SuppressWarnings("unchecked")
   public void testRoleIntegration() throws Exception {
     JettySolrRunner j0 = cluster.getJettySolrRunner(0);
-    JettySolrRunner j1 = null, j2 = null;
+    JettySolrRunner j1, j2;
     V2Response rsp = new V2Request.Builder("/cluster/node-roles/supported").GET().build().process(cluster.getSolrClient());
     Map<String, Object> l = (Map<String, Object>) rsp._get("supported-roles", Collections.emptyMap());
     assertTrue(l.containsKey("data"));
diff --git a/solr/solr-ref-guide/src/node-roles.adoc b/solr/solr-ref-guide/src/node-roles.adoc
index e5a70ec..455de14 100644
--- a/solr/solr-ref-guide/src/node-roles.adoc
+++ b/solr/solr-ref-guide/src/node-roles.adoc
@@ -45,7 +45,7 @@ Examples: `-Dsolr.node.roles=data:on,overseer:allowed` or `-Dsolr.node.roles=ove
 
 [TIP]
 ====
-If a node has been started with no `solr.node.roles` parameter, it will be assumed to have the data role turned on and overseer role allowed on it. If you've never used roles before, you likely won't need to change anything in your startup parameters to accomodate the functionality associated with these roles.
+If a node has been started with no `solr.node.roles` parameter, it will be assumed to have the data role turned on and overseer role allowed on it. If you've never used roles before, you likely won't need to change anything in your startup parameters to accommodate the functionality associated with these roles.
 ====
 
 .Supported roles
diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java b/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java
index a01cab2..8814cc9 100644
--- a/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java
+++ b/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java
@@ -103,7 +103,16 @@ public class ZkStateReader implements SolrCloseable {
   public static final String STATE_TIMESTAMP_PROP = "stateTimestamp";
   public static final String COLLECTIONS_ZKNODE = "/collections";
   public static final String LIVE_NODES_ZKNODE = "/live_nodes";
+
+  /**
+   * The following, node_roles and roles.json are for assigning roles to
+   * nodes. The node_roles is the preferred way (using -Dsolr.node.roles param),
+   * and roles.json is used by legacy ADDROLE API command.
+   * TODO: Deprecate & remove support for roles.json in an upcoming release.
+   */
   public static final String NODE_ROLES = "/node_roles";
+  public static final String ROLES = "/roles.json";
+
   public static final String ALIASES = "/aliases.json";
   /**
    * This ZooKeeper file is no longer used starting with Solr 9 but keeping the name around to check if it
@@ -125,7 +134,6 @@ public class ZkStateReader implements SolrCloseable {
   public static final String TLOG_REPLICAS = "tlogReplicas";
   public static final String READ_ONLY = "readOnly";
 
-  public static final String ROLES = "/roles.json";
 
   public static final String CONFIGS_ZKNODE = "/configs";
   public final static String CONFIGNAME_PROP = "configName";

[solr] 03/28: cleanup

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit d694418a62f79024147b8056272263f16293d60f
Author: Noble Paul <no...@gmail.com>
AuthorDate: Wed Nov 10 00:13:37 2021 +1100

    cleanup
---
 solr/core/src/java/org/apache/solr/cloud/ZkController.java | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index 8dee09e..48c26a4 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -46,9 +46,6 @@ import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
-import java.util.function.Function;
 import java.util.function.Supplier;
 
 import com.google.common.base.Strings;
@@ -67,9 +64,7 @@ import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.*;
 import org.apache.solr.common.cloud.Replica.Type;
-import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.IOUtils;

[solr] 10/28: refactor

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit fa8059e1dfe5fddd2bcf90dd9e465c6412d0c09b
Author: Noble Paul <no...@gmail.com>
AuthorDate: Thu Nov 18 13:53:40 2021 +1100

    refactor
---
 solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
index aaa9591..5a74d78 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
@@ -260,7 +260,7 @@ public class Assign {
        if (data != null && data.getData() != null && data.getData().length > 0) {
          @SuppressWarnings("unchecked")
          List<String> roles = (List<String>) Utils.fromJSON(data.getData());
-         if (roles.contains(NodeRole.Role.DATA.toString()) == false) {
+         if (!roles.contains(NodeRole.Role.DATA.toString())) {
            liveNodes.remove(name);
          }
        }

[solr] 11/28: unused import

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 0587057a94c1b5e2ffccd4c5f34881bb30d3365f
Author: Noble Paul <no...@gmail.com>
AuthorDate: Thu Nov 18 13:54:11 2021 +1100

    unused import
---
 solr/core/src/java/org/apache/solr/core/NodeRole.java | 1 -
 1 file changed, 1 deletion(-)

diff --git a/solr/core/src/java/org/apache/solr/core/NodeRole.java b/solr/core/src/java/org/apache/solr/core/NodeRole.java
index 01c96fd..a4cf757 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeRole.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeRole.java
@@ -21,7 +21,6 @@ import java.lang.invoke.MethodHandles;
 import java.util.*;
 
 import org.apache.solr.common.IteratorWriter;
-import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.StringUtils;
 import org.apache.solr.common.util.StrUtils;
 import org.slf4j.Logger;

[solr] 08/28: tests were failing intermittently

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit d4d98e58b067d108da5743f6a4f3413bdb4a9197
Author: Noble Paul <no...@gmail.com>
AuthorDate: Wed Nov 17 22:12:44 2021 +1100

    tests were failing intermittently
---
 .../org/apache/solr/cloud/OverseerNodePrioritizer.java | 18 ++++++++++++++----
 .../src/java/org/apache/solr/cloud/ZkController.java   |  9 +++------
 .../solr/cloud/api/collections/OverseerRoleCmd.java    | 11 ++++++++++-
 .../src/java/org/apache/solr/core/CoreContainer.java   |  8 ++++++++
 .../src/java/org/apache/solr/handler/ClusterAPI.java   | 13 +++++++++----
 .../src/test/org/apache/solr/cloud/NodeRolesTest.java  | 17 +++++++++++++++++
 6 files changed, 61 insertions(+), 15 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java b/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
index c02403e..9a423a2 100644
--- a/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
+++ b/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
@@ -17,15 +17,18 @@
 package org.apache.solr.cloud;
 
 import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.solr.client.solrj.impl.ZkDistribStateManager;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.CoreAdminParams.CoreAdminAction;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.handler.ClusterAPI;
 import org.apache.solr.handler.component.ShardHandler;
 import org.apache.solr.handler.component.ShardHandlerFactory;
 import org.apache.solr.handler.component.ShardRequest;
@@ -59,11 +62,18 @@ public class OverseerNodePrioritizer {
 
   public synchronized void prioritizeOverseerNodes(String overseerId) throws Exception {
     SolrZkClient zk = zkStateReader.getZkClient();
-    if(!zk.exists(ZkStateReader.ROLES,true))return;
-    Map<?,?> m = (Map<?,?>) Utils.fromJSON(zk.getData(ZkStateReader.ROLES, null, new Stat(), true));
+    List<String> overseerDesignates = new ArrayList<>();
+    if(zk.exists(ZkStateReader.ROLES,true)) {
+      Map<?,?> m = (Map<?,?>) Utils.fromJSON(zk.getData(ZkStateReader.ROLES, null, new Stat(), true));
+      @SuppressWarnings("unchecked")
+      List<String> l = (List<String>) m.get("overseer");
+      if(l != null) {
+        overseerDesignates.addAll(l);
+      }
+    }
 
-    List<?> overseerDesignates = (List<?>) m.get("overseer");
-    if(overseerDesignates==null || overseerDesignates.isEmpty()) return;
+    overseerDesignates.addAll(ClusterAPI.getNodesByRole("overseer", new ZkDistribStateManager(zkStateReader.getZkClient())));
+    if(overseerDesignates.isEmpty()) return;
     String ldr = OverseerTaskProcessor.getLeaderNode(zk);
     if(overseerDesignates.contains(ldr)) return;
     log.info("prioritizing overseer nodes at {} overseer designates are {}", overseerId, overseerDesignates);
diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index 00274aa..c955497 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -2198,10 +2198,6 @@ public class ZkController implements Closeable {
 
   public void checkOverseerDesignate() {
     try {
-      if(cc.nodeRole.role() == NodeRole.Type.overseer) {
-        setPreferredOverseer();
-        return;
-      }
       byte[] data = zkClient.getData(ZkStateReader.ROLES, null, new Stat(), true);
       if (data == null) return;
       Map<?,?> roles = (Map<?,?>) Utils.fromJSON(data);
@@ -2218,10 +2214,11 @@ public class ZkController implements Closeable {
     }
   }
 
-  private void setPreferredOverseer() throws KeeperException, InterruptedException {
+  public void setPreferredOverseer() throws KeeperException, InterruptedException {
     ZkNodeProps props = new ZkNodeProps(Overseer.QUEUE_OPERATION, ADDROLE.toString().toLowerCase(Locale.ROOT),
         "node", getNodeName(),
-        "role", "overseer");
+        "role", "overseer",
+        "persist", "false");
     log.info("Going to add role {} ", props);
     getOverseerCollectionQueue().offer(Utils.toJSON(props));
   }
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java
index e0766e6..409c480 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java
@@ -64,6 +64,10 @@ public class OverseerRoleCmd implements CollApiCmds.CollectionApiCommand {
     SolrZkClient zkClient = zkStateReader.getZkClient();
     Map<String, List<String>> roles = null;
     String node = message.getStr("node");
+    if("false".equals(message.getStr("persist"))) {// no need to persist to roles.json
+      runPrioritizer();
+      return;
+    }
 
     String roleName = message.getStr("role");
     boolean nodeExists = false;
@@ -89,16 +93,21 @@ public class OverseerRoleCmd implements CollApiCmds.CollectionApiCommand {
     } else {
       zkClient.create(ZkStateReader.ROLES, Utils.toJSON(roles), CreateMode.PERSISTENT, true);
     }
+    runPrioritizer();
+
+  }
+
+  private void runPrioritizer() {
     //if there are too many nodes this command may time out. And most likely dedicated
     // overseers are created when there are too many nodes  . So , do this operation in a separate thread
     new Thread(() -> {
       try {
+        log.info("prioritizeOverseerNodes()");
         overseerPrioritizer.prioritizeOverseerNodes(ccc.getOverseerId());
       } catch (Exception e) {
         log.error("Error in prioritizing Overseer", e);
       }
     }, "OverseerPrioritizationThread").start();
-
   }
 
 }
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index f85f3d7..40d5f89 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -947,6 +947,14 @@ public class CoreContainer {
       });
 
       clusterSingletons.setReady();
+      if(nodeRole.role() == NodeRole.Type.overseer) {
+        try {
+          log.info("This node is started as an overseer");
+          zkSys.getZkController().setPreferredOverseer();
+        } catch (KeeperException | InterruptedException e) {
+          throw new SolrException(ErrorCode.SERVER_ERROR, e);
+        }
+      }
       if (!distributedCollectionCommandRunner.isPresent()) {
         zkSys.getZkController().checkOverseerDesignate();
       }
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index 74b221f..9541191 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -17,12 +17,14 @@
 
 package org.apache.solr.handler;
 
+import java.io.IOException;
 import java.util.*;
 
 import com.google.common.collect.Maps;
 import org.apache.solr.api.Command;
 import org.apache.solr.api.EndPoint;
 import org.apache.solr.api.PayloadObj;
+import org.apache.solr.client.solrj.cloud.DistribStateManager;
 import org.apache.solr.client.solrj.request.beans.ClusterPropPayload;
 import org.apache.solr.client.solrj.request.beans.CreateConfigPayload;
 import org.apache.solr.client.solrj.request.beans.RateLimiterPayload;
@@ -43,6 +45,7 @@ import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.handler.admin.ConfigSetsHandler;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.zookeeper.KeeperException;
 
 import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
 import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
@@ -92,11 +95,13 @@ public class ClusterAPI {
           permission = COLL_READ_PERM)
   public void nodesWithRole(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     String role = req.getPathTemplateValues().get("role");
+    rsp.add("nodes",
+            getNodesByRole(role, collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager()));
+  }
 
+  public static List<String> getNodesByRole(String role, DistribStateManager zk) throws InterruptedException, IOException, KeeperException {
     List<String> result = new ArrayList<>();
-    collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager().
-            forEachChild(ZkStateReader.NODE_ROLES, (node, data) -> {
-
+    zk.forEachChild(ZkStateReader.NODE_ROLES, (node, data) -> {
       if(data != null && data.getData() != null) {
         @SuppressWarnings("unchecked")
         Map<String, Object> roleData = (Map<String, Object>) Utils.fromJSON(data.getData());
@@ -105,7 +110,7 @@ public class ClusterAPI {
         }
       }
     });
-    rsp.add("nodes", result);
+    return result;
   }
 
 
diff --git a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
index 70ab96b..42c5a0f 100644
--- a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
@@ -1,3 +1,20 @@
+/*
+ * 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.cloud;
 
 import java.lang.invoke.MethodHandles;

[solr] 15/28: SOLR-15694: Ref guide changes

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit e4e24dce8aa7540a1d1e0287ddac071ac468317d
Author: Ishan Chattopadhyaya <is...@apache.org>
AuthorDate: Fri Dec 17 14:15:01 2021 +0530

    SOLR-15694: Ref guide changes
---
 solr/solr-ref-guide/src/node-roles.adoc         | 65 +++++++++++++++++++++++++
 solr/solr-ref-guide/src/solrcloud-clusters.adoc |  4 ++
 2 files changed, 69 insertions(+)

diff --git a/solr/solr-ref-guide/src/node-roles.adoc b/solr/solr-ref-guide/src/node-roles.adoc
new file mode 100644
index 0000000..f3e5969
--- /dev/null
+++ b/solr/solr-ref-guide/src/node-roles.adoc
@@ -0,0 +1,65 @@
+= Node Roles
+// 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 node in Solr is usually capable of performing various types of operations, e.g. hosting replicas, performing indexing and querying on them, collection management tasks etc. However, if someone wants to setup a cluster where these functionalities are isolated to certain dedicated nodes, then this can be achieved leveraging the concept of node roles.
+
+== Definitions
+
+=== Node role
+
+A role is a designation of a node that indicates that the node may perform a certain functionality that is governed by the role.
+
+=== Mode
+Every role has a list of modes under which a node can be. It can be simple (e.g. `["on", "off"]`) or more granular (e.g. `["allowed", "preferred", "disallowed"]`).
+
+== Roles
+
+In order to specify role(s) for a node, one needs to start a Solr node with the following parameter.
+
+.Startup Parameter
+[cols="1,2,1,1"] 
+|===
+|Parameter |Value |Required? | Default
+
+|solr.node.roles
+|Comma separated list of roles (in the format: <role>:<mode>) for this node.
+Examples: "data:on,overseer:allowed" or "overseer:preferred"
+|No
+|data:on,overseer:allowed
+|===
+
+[TIP]
+====
+If a node has been started with no `solr.node.roles` parameter, it will be assumed to have the data role turned on and overseer role allowed on it. If you've never needed roles before, you likely won't need to change anything in your startup parameters to accomodate the functionality associated with these roles.
+====
+
+
+=== `data` role
+A node with this role (in mode "on") can host shards and replicas for collections.
+
+=== `overseer` role
+A node with this role can perform duties of an overseer node (unless mode is `disallowed`). When one or more nodes have the overseer role in `preferred` mode, the overseer leader will be elected from one of these nodes. In case no node is designated as a preferred overseer or no such node is live, the overseer leader will be elected from one of the nodes that have the overseer role in `allowed` mode. If all nodes that are designated with overseer role (allowed or preferred) are down, the [...]
+
+== A typical scenario of roles usage
+
+A common situation arises when the nodes in a cluster is under heavy querying or indexing load, and under such circumstances the overseer leader node is unable to perform collection management duties efficiently. It might be reasonable to have dedicated nodes to act as the overseer. Such an effect can be achieved as follows:
+
+* Most nodes, say 10 of them, in the cluster start with `-Dsolr.node.roles=data:on,overseer:allowed` (or with no parameter, since the default value for `solr.node.roles` is the same).
+* One or more nodes can start with `-Dsolr.node.roles=overseer:preferred` (or `-Dsolr.node.roles=overseer:preferred,data:off`)
+
+In this arrangement, such dedicated nodes can have lesser resources than other data nodes (since these are stateless nodes) and yet the cluster will behave optimally. In case the dedicated overseer nodes go down for some reason, the overseer leader will be elected from one of the data nodes (since they have overseer in "allowed" mode), and once one of the dedicated overseer nodes are back up, it will assume the overseer leadership again.
\ No newline at end of file
diff --git a/solr/solr-ref-guide/src/solrcloud-clusters.adoc b/solr/solr-ref-guide/src/solrcloud-clusters.adoc
index 83b3fde..03723f5 100644
--- a/solr/solr-ref-guide/src/solrcloud-clusters.adoc
+++ b/solr/solr-ref-guide/src/solrcloud-clusters.adoc
@@ -3,6 +3,7 @@
     solrcloud-recoveries-and-write-tolerance, \
     solrcloud-distributed-requests, \
     aliases, \
+    node-roles, \
     cluster-node-management, \
     shard-management, \
     replica-management, \
@@ -47,6 +48,9 @@ ZooKeeper is a critical component of SolrCloud, used to manage shard and replica
 | <<solrcloud-recoveries-and-write-tolerance.adoc#,SolrCloud Recoveries and Write Tolerance>>: Recovery in a SolrCloud cluster.
 | <<solrcloud-distributed-requests.adoc#,SolrCloud Distributed Requests>>: Query routing in a SolrCloud cluster.
 | <<aliases.adoc#,Aliases>>: Alternative names for collections.
+2+^h| Cluster Management
+| <<node-roles.adoc#,Node Roles>>: Functional roles for nodes for multi-tiered clusters.
+|
 2+^h| Collections API
 |<<cluster-node-management.adoc#,Cluster and Node Management>>: Cluster management commands of the Collections API.
 | <<shard-management.adoc#,Shard Management>>: Shard management commands of the Collections API.

[solr] 23/28: Ref guide fixes

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit a501b777c64d126aaeac92c22d858a6f281ed9be
Author: Ishan Chattopadhyaya <is...@apache.org>
AuthorDate: Sat Dec 18 15:40:38 2021 +0530

    Ref guide fixes
---
 solr/solr-ref-guide/src/node-roles.adoc | 29 ++++++++++++++++++++++++-----
 1 file changed, 24 insertions(+), 5 deletions(-)

diff --git a/solr/solr-ref-guide/src/node-roles.adoc b/solr/solr-ref-guide/src/node-roles.adoc
index ed2c07a..19c5beb 100644
--- a/solr/solr-ref-guide/src/node-roles.adoc
+++ b/solr/solr-ref-guide/src/node-roles.adoc
@@ -37,10 +37,10 @@ In order to specify role(s) for a node, one needs to start a Solr node with the
 |Parameter |Value |Required? | Default
 
 |solr.node.roles
-|Comma separated list of roles (in the format: <role>:<mode>) for this node.
-Examples: "data:on,overseer:allowed" or "overseer:preferred"
+|Comma separated list of roles (in the format: `<role>:<mode>`) for this node.
+Examples: `-Dsolr.node.roles=data:on,overseer:allowed` or `-Dsolr.node.roles=overseer:preferred`
 |No
-|data:on,overseer:allowed
+|`data:on,overseer:allowed`
 |===
 
 [TIP]
@@ -48,6 +48,17 @@ Examples: "data:on,overseer:allowed" or "overseer:preferred"
 If a node has been started with no `solr.node.roles` parameter, it will be assumed to have the data role turned on and overseer role allowed on it. If you've never needed roles before, you likely won't need to change anything in your startup parameters to accomodate the functionality associated with these roles.
 ====
 
+.Supported roles
+[cols="1,1"] 
+|===
+|Role |Modes
+
+|`data`
+|on, off
+
+|`overseer`
+|allowed, preferred, disallowed
+|===
 
 === `data` role
 A node with this role (in mode "on") can host shards and replicas for collections.
@@ -55,9 +66,9 @@ A node with this role (in mode "on") can host shards and replicas for collection
 === `overseer` role
 A node with this role can perform duties of an overseer node (unless mode is `disallowed`). When one or more nodes have the overseer role in `preferred` mode, the overseer leader will be elected from one of these nodes. In case no node is designated as a preferred overseer or no such node is live, the overseer leader will be elected from one of the nodes that have the overseer role in `allowed` mode. If all nodes that are designated with overseer role (allowed or preferred) are down, the [...]
 
-== A typical scenario of roles usage
+== Example usage
 
-A common situation arises when the nodes in a cluster is under heavy querying or indexing load, and under such circumstances the overseer leader node is unable to perform collection management duties efficiently. It might be reasonable to have dedicated nodes to act as the overseer. Such an effect can be achieved as follows:
+Sometimes, when the nodes in a cluster is under heavy querying or indexing load, the overseer leader node is unable to perform collection management duties efficiently. It might be reasonable to have dedicated nodes to act as the overseer. Such an effect can be achieved as follows:
 
 * Most nodes, say 10 of them, in the cluster start with `-Dsolr.node.roles=data:on,overseer:allowed` (or with no parameter, since the default value for `solr.node.roles` is the same).
 * One or more nodes can start with `-Dsolr.node.roles=overseer:preferred` (or `-Dsolr.node.roles=overseer:preferred,data:off`)
@@ -68,6 +79,8 @@ In this arrangement, such dedicated nodes can have lesser resources than other d
 
 === GET /api/cluster/node-roles/supported
 
+Fetches the list of supported roles and their supported modes for this cluster.
+
 *Input*
 [source,text]
 ----
@@ -96,6 +109,8 @@ curl http://localhost:8983/api/cluster/node-roles/supported
 
 === GET /api/cluster/node-roles
 
+Fetches the current node roles assignment for all the nodes in the cluster.
+
 *Input*
 [source,text]
 ----
@@ -122,6 +137,8 @@ curl http://localhost:8983/api/cluster/node-roles
 
 === GET /api/cluster/node-roles/{role}
 
+Fetches the current node roles assignment for a specified role.
+
 *Input*
 [source,text]
 ----
@@ -144,6 +161,8 @@ http://localhost:8983/api/node-roles/data
 
 === GET /api/cluster/node-roles/nodes/{node}
 
+Fetches the current node roles assignment for the specified node.
+
 *Input*
 [source,text]
 ----

[solr] 19/28: removed unnecessary changes

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 1cf58d7acdb16463f764901ec83c75dfb84056ca
Author: Noble Paul <no...@gmail.com>
AuthorDate: Fri Dec 17 16:40:09 2021 +1100

    removed unnecessary changes
---
 .../src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java
index 35c4bd6..191e33b 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java
@@ -102,12 +102,12 @@ public class OverseerRoleCmd implements CollApiCmds.CollectionApiCommand {
     // overseers are created when there are too many nodes  . So , do this operation in a separate thread
     new Thread(() -> {
       try {
-        log.info("prioritizeOverseerNodes()");
         overseerPrioritizer.prioritizeOverseerNodes(ccc.getOverseerId());
       } catch (Exception e) {
         log.error("Error in prioritizing Overseer", e);
       }
     }, "OverseerPrioritizationThread").start();
+
   }
 
 }

[solr] 09/28: Multiple roles supported per node

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 84982ef4308451a4ff8096b6f479aa1e62603f11
Author: Ishan Chattopadhyaya <is...@apache.org>
AuthorDate: Wed Nov 17 19:14:34 2021 +0530

    Multiple roles supported per node
---
 .../apache/solr/cloud/OverseerNodePrioritizer.java |  6 +-
 .../java/org/apache/solr/cloud/ZkController.java   |  9 ++-
 .../apache/solr/cloud/api/collections/Assign.java  |  9 ++-
 .../cloud/api/collections/OverseerRoleCmd.java     |  2 +-
 .../java/org/apache/solr/core/CoreContainer.java   |  4 +-
 .../src/java/org/apache/solr/core/NodeRole.java    | 69 +++++++++++---------
 .../java/org/apache/solr/handler/ClusterAPI.java   | 17 +++--
 .../test/org/apache/solr/cloud/NodeRolesTest.java  | 73 +++++++++-------------
 .../client/solrj/cloud/DistribStateManager.java    | 12 ++--
 9 files changed, 98 insertions(+), 103 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java b/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
index 9a423a2..f88c686 100644
--- a/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
+++ b/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
@@ -63,17 +63,17 @@ public class OverseerNodePrioritizer {
   public synchronized void prioritizeOverseerNodes(String overseerId) throws Exception {
     SolrZkClient zk = zkStateReader.getZkClient();
     List<String> overseerDesignates = new ArrayList<>();
-    if(zk.exists(ZkStateReader.ROLES,true)) {
+    if (zk.exists(ZkStateReader.ROLES,true)) {
       Map<?,?> m = (Map<?,?>) Utils.fromJSON(zk.getData(ZkStateReader.ROLES, null, new Stat(), true));
       @SuppressWarnings("unchecked")
       List<String> l = (List<String>) m.get("overseer");
-      if(l != null) {
+      if (l != null) {
         overseerDesignates.addAll(l);
       }
     }
 
     overseerDesignates.addAll(ClusterAPI.getNodesByRole("overseer", new ZkDistribStateManager(zkStateReader.getZkClient())));
-    if(overseerDesignates.isEmpty()) return;
+    if (overseerDesignates.isEmpty()) return;
     String ldr = OverseerTaskProcessor.getLeaderNode(zk);
     if(overseerDesignates.contains(ldr)) return;
     log.info("prioritizing overseer nodes at {} overseer designates are {}", overseerId, overseerDesignates);
diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index c955497..05c6c5b 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -79,7 +79,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.CoreDescriptor;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrCoreInitializationException;
-import org.apache.solr.core.NodeRole;
 import org.apache.solr.handler.component.HttpShardHandler;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -1088,11 +1087,11 @@ public class ZkController implements Closeable {
     log.info("Register node as live in ZooKeeper:{}", nodePath);
     List<Op> ops = new ArrayList<>(2);
     ops.add(Op.create(nodePath, null, zkClient.getZkACLProvider().getACLsToAdd(nodePath), CreateMode.EPHEMERAL));
-    if(cc.nodeRole.role() != NodeRole.Type.data) {
-      //this is a non-data node
-      ops.add(Op.create(ZkStateReader.NODE_ROLES + "/" + nodeName, Utils.toJSON(cc.nodeRole), zkClient.getZkACLProvider().getACLsToAdd(nodePath), CreateMode.EPHEMERAL));
 
-    }
+    // Create the roles node as well
+    ops.add(Op.create(ZkStateReader.NODE_ROLES + "/" + nodeName,
+            Utils.toJSON(cc.nodeRoles), zkClient.getZkACLProvider().getACLsToAdd(nodePath), CreateMode.EPHEMERAL));
+
     zkClient.multi(ops, true);
   }
 
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
index 5f726c9..aaa9591 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
@@ -257,17 +257,16 @@ public class Assign {
   public static void filterNonDataNodes(DistribStateManager zk, List<String> liveNodes) {
     try {
      zk.forEachChild(ZkStateReader.NODE_ROLES, (name, data) -> {
-       if(data != null && data.getData() != null && data.getData().length >0) {
+       if (data != null && data.getData() != null && data.getData().length > 0) {
          @SuppressWarnings("unchecked")
-         Map<String,Object> map = (Map<String, Object>) Utils.fromJSON(data.getData());
-         if(Boolean.FALSE.equals(map.get(NodeRole.HAS_DATA))) {
+         List<String> roles = (List<String>) Utils.fromJSON(data.getData());
+         if (roles.contains(NodeRole.Role.DATA.toString()) == false) {
            liveNodes.remove(name);
          }
        }
      });
-
     } catch (Exception e) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to communicate with Zookeeper");
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Problem fetching roles from Zookeeper", e);
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java
index 409c480..35c4bd6 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerRoleCmd.java
@@ -64,7 +64,7 @@ public class OverseerRoleCmd implements CollApiCmds.CollectionApiCommand {
     SolrZkClient zkClient = zkStateReader.getZkClient();
     Map<String, List<String>> roles = null;
     String node = message.getStr("node");
-    if("false".equals(message.getStr("persist"))) {// no need to persist to roles.json
+    if ("false".equals(message.getStr("persist"))) {// no need to persist to roles.json
       runPrioritizer();
       return;
     }
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 40d5f89..44c63db 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -246,7 +246,7 @@ public class CoreContainer {
 
   private final ObjectCache objectCache = new ObjectCache();
 
-  public final NodeRole nodeRole = new NodeRole(System.getProperty(NodeRole.NODE_ROLE));
+  public final NodeRole nodeRoles = new NodeRole(System.getProperty(NodeRole.NODE_ROLES_PROP));
 
   private final ClusterSingletons clusterSingletons = new ClusterSingletons(
       () -> getZkController() != null &&
@@ -947,7 +947,7 @@ public class CoreContainer {
       });
 
       clusterSingletons.setReady();
-      if(nodeRole.role() == NodeRole.Type.overseer) {
+      if (nodeRoles.getRoles().contains(NodeRole.Role.OVERSEER)) {
         try {
           log.info("This node is started as an overseer");
           zkSys.getZkController().setPreferredOverseer();
diff --git a/solr/core/src/java/org/apache/solr/core/NodeRole.java b/solr/core/src/java/org/apache/solr/core/NodeRole.java
index f0dd794..01c96fd 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeRole.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeRole.java
@@ -17,53 +17,62 @@
 package org.apache.solr.core;
 
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.util.*;
+
+import org.apache.solr.common.IteratorWriter;
 import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.StringUtils;
 import org.apache.solr.common.util.StrUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NodeRole implements IteratorWriter {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  public static final String NODE_ROLES_PROP = "solr.node.roles";
 
-public class NodeRole implements MapWriter {
-  private boolean hasData = true;
-  private Type role  =Type.data;
+  private final Set<Role> roles;
 
   public NodeRole(String role) {
     if (StringUtils.isEmpty(role)) {
+      // if no roles were specified, assume "data" role for backcompat reasons
+      roles = Set.of(Role.DATA);
       return;
     }
-    Set<String> roles = new HashSet<>(StrUtils.split(role, ','));
-    if (roles.isEmpty()) return;
-    for (String s : roles) {
-      if (Type.data.name().equals(s)) {
-        hasData = true;
-        continue;
-      }
-      if (Type.overseer.name().equals(s)) {
-        this.role = Type.overseer;
-        hasData = false;
-      } else {
-        throw new RuntimeException("Unknown type");
-      }
+    Set<String> rolesSet = new HashSet<>(StrUtils.split(role, ','));
+    if (rolesSet.isEmpty()) {
+      // if no roles were specified, assume "data" role for backcompat reasons
+      roles = Set.of(Role.DATA);
+      return;
+    }
+    roles = new TreeSet<>();
+    for (String r: rolesSet) {
+      roles.add(Role.valueOfCaseInsensitive(r));
     }
-
-  }
-
-  public boolean hasData() {
-    return hasData;
   }
 
-  public Type role() {
-    return role;
+  public Set<Role> getRoles() {
+    return roles;
   }
 
   @Override
-  public void writeMap(EntryWriter ew) throws IOException {
-    ew.put(HAS_DATA, hasData);
-    ew.put("role", role.name());
+  public void writeIter(ItemWriter iw) throws IOException {
+    for (Role role: roles) iw.add(role.toString());
   }
 
-  public enum Type {
-    data, overseer;
+  public enum Role {
+    DATA, OVERSEER;
+
+    public static Role valueOfCaseInsensitive(String value) {
+      // Given a user string "overseer", convert to OVERSEER and return the enum value
+      String canonicalValue = value.toUpperCase().replace(' ', '_');
+      return Role.valueOf(canonicalValue);
+    }
+
+    @Override
+    public String toString() {
+      return super.toString().toLowerCase();
+    }
   }
-  public static final String HAS_DATA = "hasData";
-  public static final String NODE_ROLE = "solr.node.role";
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index 9541191..5f512d2 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -18,6 +18,7 @@
 package org.apache.solr.handler;
 
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.util.*;
 
 import com.google.common.collect.Maps;
@@ -46,6 +47,8 @@ import org.apache.solr.handler.admin.ConfigSetsHandler;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.zookeeper.KeeperException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
 import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
@@ -74,17 +77,19 @@ public class ClusterAPI {
     this.collectionsHandler = ch;
     this.configSetsHandler = configSetsHandler;
   }
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   @EndPoint(method = GET,
           path = "/cluster/node-roles",
           permission = COLL_READ_PERM)
   @SuppressWarnings("unchecked")
+  // nocommit: it must also output all data nodes that didn't start with -Dsolr.node.roles
   public void roles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
-    Map <String, Map<String,Object>> result = new LinkedHashMap<>();
+    Map <String, List<String>> result = new LinkedHashMap<>();
     collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager().
             forEachChild(ZkStateReader.NODE_ROLES, (node, data) -> {
-      if(data != null && data.getData() != null) {
-        result.put(node, (Map<String, Object>) Utils.fromJSON(data.getData()));
+      if (data != null && data.getData() != null) {
+        result.put(node, ((List<String>) Utils.fromJSON(data.getData())));
       }
     });
     rsp.add("node-roles", result);
@@ -102,10 +107,10 @@ public class ClusterAPI {
   public static List<String> getNodesByRole(String role, DistribStateManager zk) throws InterruptedException, IOException, KeeperException {
     List<String> result = new ArrayList<>();
     zk.forEachChild(ZkStateReader.NODE_ROLES, (node, data) -> {
-      if(data != null && data.getData() != null) {
+      if (data != null && data.getData() != null) {
         @SuppressWarnings("unchecked")
-        Map<String, Object> roleData = (Map<String, Object>) Utils.fromJSON(data.getData());
-        if(role.equals(roleData.get("role"))) {
+        List<String> rolesData = (List<String>) Utils.fromJSON(data.getData());
+        if (rolesData.contains(role)) {
           result.add(node);
         }
       }
diff --git a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
index 42c5a0f..e5f2ee1 100644
--- a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
@@ -19,6 +19,8 @@ package org.apache.solr.cloud;
 
 import java.lang.invoke.MethodHandles;
 import java.util.List;
+import java.util.TreeSet;
+
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.V2Request;
@@ -49,47 +51,20 @@ public class NodeRolesTest extends SolrCloudTestCase {
 
     JettySolrRunner j0 = cluster.getJettySolrRunner(0);
     JettySolrRunner j1 = null, j2 = null;
-    System.setProperty(NodeRole.NODE_ROLE, "overseer");
-    try {
-      j1 = cluster.startJettySolrRunner();
-    } finally {
-      System.clearProperty(NodeRole.NODE_ROLE);
-    }
+    j1 = startNodeWithRoles("overseer");
 
-    V2Response rsp = new V2Request
-            .Builder("/cluster/node-roles")
-            .GET()
-            .build()
-            .process(cluster.getSolrClient());
-    assertEquals(Boolean.FALSE, rsp._get(List.of("node-roles", j1.getNodeName(), NodeRole.HAS_DATA), null));
-    assertEquals("overseer", rsp._get(List.of("node-roles", j1.getNodeName(), "role"), null));
+    V2Response rsp = new V2Request.Builder("/cluster/node-roles").GET().build().process(cluster.getSolrClient());
+    assertEquals(List.of("overseer"), rsp._get(List.of("node-roles", j1.getNodeName()), null));
 
     OverseerRolesTest.waitForNewOverseer(20, j1.getNodeName(), false);
-    //start another node that is overseer but has data
-    System.setProperty(NodeRole.NODE_ROLE, "overseer,data");
-    try {
-      j2 = cluster.startJettySolrRunner();
-    } finally {
-      System.clearProperty(NodeRole.NODE_ROLE);
-    }
-    rsp = new V2Request
-            .Builder("/cluster/node-roles")
-            .GET()
-            .build()
-            .process(cluster.getSolrClient());
 
-    assertEquals(Boolean.TRUE, rsp._get(List.of("node-roles", j2.getNodeName(), NodeRole.HAS_DATA), null));
-    assertEquals("overseer", rsp._get(List.of("node-roles", j2.getNodeName(), "role"), null));
-
-    rsp = new V2Request
-            .Builder("/cluster/node-roles/overseer")
-            .GET()
-            .build()
-            .process(cluster.getSolrClient());
-    List<String> overseerNodes = (List<String>) rsp._get("nodes", null);
-    assertTrue(overseerNodes.contains(j1.getNodeName()));
-    assertTrue(overseerNodes.contains(j2.getNodeName()));
+    //start another node that is overseer but has data
+    j2 = startNodeWithRoles("overseer,data");
+    rsp = new V2Request.Builder("/cluster/node-roles").GET().build().process(cluster.getSolrClient());
+    assertEquals(List.of("data", "overseer"), rsp._get(List.of("node-roles", j2.getNodeName()), null));
 
+    rsp = new V2Request.Builder("/cluster/node-roles/overseer").GET().build().process(cluster.getSolrClient());
+    assertEquals(new TreeSet<String>(List.of(j1.getNodeName(), j2.getNodeName())), new TreeSet<String>((List<String>) rsp._get("nodes", null)));
 
     String COLLECTION_NAME = "TEST_ROLES";
     CollectionAdminRequest
@@ -97,18 +72,26 @@ public class NodeRolesTest extends SolrCloudTestCase {
             .process(cluster.getSolrClient());
     cluster.waitForActiveCollection(COLLECTION_NAME, 3, 3);
 
-    String overseer = j1.getNodeName();
+    // Assert that no replica was placed on the dedicated overseer node
+    String dedicatedOverseer = j1.getNodeName();
     cluster.getSolrClient().getClusterStateProvider().getCollection(COLLECTION_NAME)
-            .forEachReplica((s, replica) -> assertNotEquals(replica.node, overseer));
+            .forEachReplica((s, replica) -> assertNotEquals(replica.node, dedicatedOverseer));
 
-    //now  shutdown the overseer
+    // Shutdown the dedicated overseer, make sure that node disappears from the roles output
     j1.stop();
-    rsp = new V2Request
-            .Builder("/cluster/node-roles")
-            .GET()
-            .build()
-            .process(cluster.getSolrClient());
-    assertNull(rsp._get(List.of("node-roles", overseer), null));
+    rsp = new V2Request.Builder("/cluster/node-roles").GET().build().process(cluster.getSolrClient());
+    assertNull(rsp._get(List.of("node-roles", dedicatedOverseer), null));
+  }
+
+  private JettySolrRunner startNodeWithRoles(String roles) throws Exception {
+    JettySolrRunner jetty;
+    System.setProperty(NodeRole.NODE_ROLES_PROP, roles);
+    try {
+      jetty = cluster.startJettySolrRunner();
+    } finally {
+      System.clearProperty(NodeRole.NODE_ROLES_PROP);
+    }
+    return jetty;
   }
 
 }
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/DistribStateManager.java b/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/DistribStateManager.java
index ebd4db7..84010c8 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/DistribStateManager.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/DistribStateManager.java
@@ -143,21 +143,21 @@ public interface DistribStateManager extends SolrCloseable {
       }
     }
   }
-  default void  forEachChild(String path,  BiConsumer<String, VersionedData> fun)
+  default void forEachChild(String path, BiConsumer<String, VersionedData> fun)
           throws InterruptedException, IOException, KeeperException {
     List<String> children = null;
     try {
-      children = listData(ZkStateReader.NODE_ROLES);
+      children = listData(path);
     } catch (NoSuchElementException e) {
       return;
     }
-    if(!children.isEmpty()) {
-      for (String node : children) {
+    if (!children.isEmpty()) {
+      for (String node: children) {
         VersionedData data = null;
         try {
-          data = getData(ZkStateReader.NODE_ROLES + "/" + node);
+          data = getData(path + "/" + node);
         } catch (NoSuchElementException e) {
-          //this node probably was deleted
+          // this node must've been deleted
           continue;
         }
         fun.accept(node, data);

[solr] 07/28: added test

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 71c11f9abf8a06dda53deff079a8cddb1c1e955d
Author: Noble Paul <no...@gmail.com>
AuthorDate: Wed Nov 17 16:55:19 2021 +1100

    added test
---
 .../apache/solr/cloud/api/collections/Assign.java  | 24 +++++-
 .../cloud/api/collections/CreateCollectionCmd.java |  2 +-
 .../solr/cloud/api/collections/RestoreCmd.java     |  2 +-
 .../impl/SimpleClusterAbstractionsImpl.java        | 34 +-------
 .../java/org/apache/solr/core/CoreContainer.java   |  2 +-
 .../src/java/org/apache/solr/core/NodeRole.java    | 15 ++--
 .../java/org/apache/solr/handler/ClusterAPI.java   |  3 +-
 .../test/org/apache/solr/cloud/NodeRolesTest.java  | 97 ++++++++++++++++++++++
 .../org/apache/solr/cloud/OverseerRolesTest.java   |  4 +-
 9 files changed, 133 insertions(+), 50 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
index c6702ef..5f726c9 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/Assign.java
@@ -51,7 +51,9 @@ import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.NodeRole;
 import org.apache.solr.util.NumberUtils;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
@@ -227,7 +229,9 @@ public class Assign {
     return false;
   }
 
-  public static List<String> getLiveOrLiveAndCreateNodeSetList(final Set<String> liveNodes, final ZkNodeProps message, final Random random) {
+  public static List<String> getLiveOrLiveAndCreateNodeSetList(final Set<String> liveNodes, final ZkNodeProps message, final Random random,
+                                                               DistribStateManager zk) {
+
     List<String> nodeList;
     final String createNodeSetStr = message.getStr(CREATE_NODE_SET);
     final List<String> createNodeList = (createNodeSetStr == null) ? null :
@@ -243,12 +247,30 @@ public class Assign {
       }
     } else {
       nodeList = new ArrayList<>(liveNodes);
+      filterNonDataNodes(zk, nodeList);
       Collections.shuffle(nodeList, random);
     }
 
     return nodeList;
   }
 
+  public static void filterNonDataNodes(DistribStateManager zk, List<String> liveNodes) {
+    try {
+     zk.forEachChild(ZkStateReader.NODE_ROLES, (name, data) -> {
+       if(data != null && data.getData() != null && data.getData().length >0) {
+         @SuppressWarnings("unchecked")
+         Map<String,Object> map = (Map<String, Object>) Utils.fromJSON(data.getData());
+         if(Boolean.FALSE.equals(map.get(NodeRole.HAS_DATA))) {
+           liveNodes.remove(name);
+         }
+       }
+     });
+
+    } catch (Exception e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to communicate with Zookeeper");
+    }
+  }
+
   static class ReplicaCount {
     public final String nodeName;
     public int thisCollectionNodes = 0;
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java
index 66c21b6..e8a185e 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java
@@ -406,7 +406,7 @@ public class CreateCollectionCmd implements CollApiCmds.CollectionApiCommand {
     // but (for now) require that each core goes on a distinct node.
 
     List<ReplicaPosition> replicaPositions;
-    List<String> nodeList = Assign.getLiveOrLiveAndCreateNodeSetList(clusterState.getLiveNodes(), message, CollectionHandlingUtils.RANDOM);
+    List<String> nodeList = Assign.getLiveOrLiveAndCreateNodeSetList(clusterState.getLiveNodes(), message, CollectionHandlingUtils.RANDOM, cloudManager.getDistribStateManager());
     if (nodeList.isEmpty()) {
       log.warn("It is unusual to create a collection ({}) without cores.", collectionName);
 
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/RestoreCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/RestoreCmd.java
index 2732cb9..af247de 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/RestoreCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/RestoreCmd.java
@@ -182,7 +182,7 @@ public class RestoreCmd implements CollApiCmds.CollectionApiCommand {
 
       this.shardHandler = ccc.newShardHandler();
       this.nodeList = Assign.getLiveOrLiveAndCreateNodeSetList(
-              zkStateReader.getClusterState().getLiveNodes(), message, CollectionHandlingUtils.RANDOM);
+              zkStateReader.getClusterState().getLiveNodes(), message, CollectionHandlingUtils.RANDOM, container.getZkController().getSolrCloudManager().getDistribStateManager());
     }
 
     @Override
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
index 16ce53c..48f3f50 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
@@ -19,23 +19,16 @@ package org.apache.solr.cluster.placement.impl;
 
 import java.io.IOException;
 import java.util.*;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 
 import com.google.common.collect.Maps;
 import org.apache.solr.client.solrj.cloud.SolrCloudManager;
-import org.apache.solr.client.solrj.cloud.VersionedData;
 import org.apache.solr.cluster.*;
-import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Slice;
-import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.CollectionAdminParams;
 import org.apache.solr.common.util.Pair;
-import org.apache.solr.common.util.Utils;
-import org.apache.zookeeper.KeeperException;
 
 import javax.annotation.Nonnull;
 
@@ -63,8 +56,7 @@ class SimpleClusterAbstractionsImpl {
     private final ClusterState clusterState;
 
     ClusterImpl(SolrCloudManager solrCloudManager) throws IOException {
-      Set<String> liveNodes = filterNonDataNodes(solrCloudManager);
-      this.liveNodes = NodeImpl.getNodes(liveNodes);
+      liveNodes = NodeImpl.getNodes(solrCloudManager.getClusterStateProvider().getLiveNodes());
       clusterState = solrCloudManager.getClusterStateProvider().getClusterState();
     }
 
@@ -90,30 +82,6 @@ class SimpleClusterAbstractionsImpl {
     }
   }
 
-  private static Set<String> filterNonDataNodes(SolrCloudManager solrCloudManager) {
-    Set<String> liveNodes = solrCloudManager.getClusterStateProvider().getLiveNodes();
-    AtomicReference<Set<String>> liveNodesCopy = new AtomicReference<>(liveNodes);
-    try {
-     solrCloudManager.getDistribStateManager().forEachChild(ZkStateReader.NODE_ROLES, (name, data) -> {
-       if(data != null && data.getData() != null && data.getData().length >0) {
-         @SuppressWarnings("unchecked")
-         Map<String,Object> map = (Map<String, Object>) Utils.fromJSON(data.getData());
-         if(Boolean.FALSE.equals(map.get("hasData"))) {
-           if(liveNodesCopy.get() == liveNodes) {
-             //the map provided should not be modified. So we make a copy
-             liveNodesCopy.set(new HashSet<>(liveNodes));
-           }
-           liveNodesCopy.get().remove(name);
-         }
-       }
-     });
-
-    } catch (Exception e) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to communicate with Zookeeper");
-    }
-    return liveNodesCopy.get();
-  }
-
 
   static class NodeImpl implements Node {
     public final String nodeName;
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index f2c2b78..f85f3d7 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -246,7 +246,7 @@ public class CoreContainer {
 
   private final ObjectCache objectCache = new ObjectCache();
 
-  public final NodeRole nodeRole = new NodeRole(System.getProperty("node.roles"));
+  public final NodeRole nodeRole = new NodeRole(System.getProperty(NodeRole.NODE_ROLE));
 
   private final ClusterSingletons clusterSingletons = new ClusterSingletons(
       () -> getZkController() != null &&
diff --git a/solr/core/src/java/org/apache/solr/core/NodeRole.java b/solr/core/src/java/org/apache/solr/core/NodeRole.java
index 7feb742..f0dd794 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeRole.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeRole.java
@@ -31,10 +31,7 @@ public class NodeRole implements MapWriter {
       return;
     }
     Set<String> roles = new HashSet<>(StrUtils.split(role, ','));
-    if (roles.isEmpty()) {
-      hasData = true;
-      return;
-    }
+    if (roles.isEmpty()) return;
     for (String s : roles) {
       if (Type.data.name().equals(s)) {
         hasData = true;
@@ -42,6 +39,7 @@ public class NodeRole implements MapWriter {
       }
       if (Type.overseer.name().equals(s)) {
         this.role = Type.overseer;
+        hasData = false;
       } else {
         throw new RuntimeException("Unknown type");
       }
@@ -59,16 +57,13 @@ public class NodeRole implements MapWriter {
 
   @Override
   public void writeMap(EntryWriter ew) throws IOException {
-    ew.put("hasData", hasData);
+    ew.put(HAS_DATA, hasData);
     ew.put("role", role.name());
   }
 
   public enum Type {
     data, overseer;
   }
-
-
-
-  public static final String NO_REPLICAS = "no-replicas";
-
+  public static final String HAS_DATA = "hasData";
+  public static final String NODE_ROLE = "solr.node.role";
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index 6f6773d..74b221f 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -75,6 +75,7 @@ public class ClusterAPI {
   @EndPoint(method = GET,
           path = "/cluster/node-roles",
           permission = COLL_READ_PERM)
+  @SuppressWarnings("unchecked")
   public void roles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     Map <String, Map<String,Object>> result = new LinkedHashMap<>();
     collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager().
@@ -89,7 +90,7 @@ public class ClusterAPI {
   @EndPoint(method = GET,
           path = "/cluster/node-roles/{role}",
           permission = COLL_READ_PERM)
-  public void rolesOfNode(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+  public void nodesWithRole(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     String role = req.getPathTemplateValues().get("role");
 
     List<String> result = new ArrayList<>();
diff --git a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
new file mode 100644
index 0000000..70ab96b
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
@@ -0,0 +1,97 @@
+package org.apache.solr.cloud;
+
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import org.apache.solr.client.solrj.embedded.JettySolrRunner;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.V2Request;
+import org.apache.solr.client.solrj.response.V2Response;
+import org.apache.solr.core.NodeRole;
+import org.junit.After;
+import org.junit.Before;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NodeRolesTest extends SolrCloudTestCase {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  @Before
+  public void setupCluster() throws Exception {
+    configureCluster(1)
+            .addConfig("conf", configset("cloud-minimal"))
+            .configure();
+  }
+
+  @After
+  public void tearDownCluster() throws Exception {
+    shutdownCluster();
+  }
+
+  @SuppressWarnings("unchecked")
+  public void testRoleIntegration() throws Exception {
+
+    JettySolrRunner j0 = cluster.getJettySolrRunner(0);
+    JettySolrRunner j1 = null, j2 = null;
+    System.setProperty(NodeRole.NODE_ROLE, "overseer");
+    try {
+      j1 = cluster.startJettySolrRunner();
+    } finally {
+      System.clearProperty(NodeRole.NODE_ROLE);
+    }
+
+    V2Response rsp = new V2Request
+            .Builder("/cluster/node-roles")
+            .GET()
+            .build()
+            .process(cluster.getSolrClient());
+    assertEquals(Boolean.FALSE, rsp._get(List.of("node-roles", j1.getNodeName(), NodeRole.HAS_DATA), null));
+    assertEquals("overseer", rsp._get(List.of("node-roles", j1.getNodeName(), "role"), null));
+
+    OverseerRolesTest.waitForNewOverseer(20, j1.getNodeName(), false);
+    //start another node that is overseer but has data
+    System.setProperty(NodeRole.NODE_ROLE, "overseer,data");
+    try {
+      j2 = cluster.startJettySolrRunner();
+    } finally {
+      System.clearProperty(NodeRole.NODE_ROLE);
+    }
+    rsp = new V2Request
+            .Builder("/cluster/node-roles")
+            .GET()
+            .build()
+            .process(cluster.getSolrClient());
+
+    assertEquals(Boolean.TRUE, rsp._get(List.of("node-roles", j2.getNodeName(), NodeRole.HAS_DATA), null));
+    assertEquals("overseer", rsp._get(List.of("node-roles", j2.getNodeName(), "role"), null));
+
+    rsp = new V2Request
+            .Builder("/cluster/node-roles/overseer")
+            .GET()
+            .build()
+            .process(cluster.getSolrClient());
+    List<String> overseerNodes = (List<String>) rsp._get("nodes", null);
+    assertTrue(overseerNodes.contains(j1.getNodeName()));
+    assertTrue(overseerNodes.contains(j2.getNodeName()));
+
+
+    String COLLECTION_NAME = "TEST_ROLES";
+    CollectionAdminRequest
+            .createCollection(COLLECTION_NAME, "conf", 3, 1)
+            .process(cluster.getSolrClient());
+    cluster.waitForActiveCollection(COLLECTION_NAME, 3, 3);
+
+    String overseer = j1.getNodeName();
+    cluster.getSolrClient().getClusterStateProvider().getCollection(COLLECTION_NAME)
+            .forEachReplica((s, replica) -> assertNotEquals(replica.node, overseer));
+
+    //now  shutdown the overseer
+    j1.stop();
+    rsp = new V2Request
+            .Builder("/cluster/node-roles")
+            .GET()
+            .build()
+            .process(cluster.getSolrClient());
+    assertNull(rsp._get(List.of("node-roles", overseer), null));
+  }
+
+}
diff --git a/solr/core/src/test/org/apache/solr/cloud/OverseerRolesTest.java b/solr/core/src/test/org/apache/solr/cloud/OverseerRolesTest.java
index c437a30..508e74e 100644
--- a/solr/core/src/test/org/apache/solr/cloud/OverseerRolesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/OverseerRolesTest.java
@@ -54,7 +54,7 @@ public class OverseerRolesTest extends SolrCloudTestCase {
     shutdownCluster();
   }
   
-  private void waitForNewOverseer(int seconds, Predicate<String> state, boolean failOnIntermediateTransition) throws Exception {
+  private static void waitForNewOverseer(int seconds, Predicate<String> state, boolean failOnIntermediateTransition) throws Exception {
     TimeOut timeout = new TimeOut(seconds, TimeUnit.SECONDS, TimeSource.NANO_TIME);
     String current = null;
     while (timeout.hasTimedOut() == false) {
@@ -72,7 +72,7 @@ public class OverseerRolesTest extends SolrCloudTestCase {
     fail("Timed out waiting for overseer state change. The current overseer is: "+current);
   }
 
-  private void waitForNewOverseer(int seconds, String expected, boolean failOnIntermediateTransition) throws Exception {
+  public static void waitForNewOverseer(int seconds, String expected, boolean failOnIntermediateTransition) throws Exception {
     log.info("Expecting node: {}", expected);
     waitForNewOverseer(seconds, s -> Objects.equals(s, expected), failOnIntermediateTransition);
   }

[solr] 04/28: suppress warnings

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 907d1f1f3395b55bd69e4beb6b4372e7c2807a96
Author: Noble Paul <no...@gmail.com>
AuthorDate: Wed Nov 10 17:08:13 2021 +1100

    suppress warnings
---
 .../solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java       | 1 +
 1 file changed, 1 insertion(+)

diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
index 1d5bec6..d7453d6 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
@@ -61,6 +61,7 @@ class SimpleClusterAbstractionsImpl {
     private final Set<Node> liveNodes;
     private final ClusterState clusterState;
 
+    @SuppressWarnings("unchecked")
     ClusterImpl(SolrCloudManager solrCloudManager) throws IOException {
       Set<String> liveNodes = solrCloudManager.getClusterStateProvider().getLiveNodes();
       try {

[solr] 28/28: cleanup and more test assertions

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 60c41705e38fb74000708e730799dff5c5b6b51b
Author: Noble Paul <no...@gmail.com>
AuthorDate: Tue Dec 28 13:41:22 2021 +1100

    cleanup and more test assertions
---
 .../src/java/org/apache/solr/core/NodeRoles.java   | 19 +++---
 .../test/org/apache/solr/cloud/NodeRolesTest.java  | 67 +++++++++++++---------
 .../org/apache/solr/cloud/OverseerRolesTest.java   |  2 +-
 solr/solr-ref-guide/src/node-roles.adoc            |  2 -
 .../apache/solr/common/cloud/ZkStateReader.java    |  2 +-
 5 files changed, 49 insertions(+), 43 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/core/NodeRoles.java b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
index 317f771..2eb2b62 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeRoles.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
@@ -30,12 +30,9 @@ public class NodeRoles {
 
   public static final String NODE_ROLES_PROP = "solr.node.roles";
 
-  /*public static final String ON =  "on";
-  public static final String OFF =  "off";
-  public static final String ALLOWED =  "allowed";
-  public static final String DISALLOWED =  "disallowed";
-  public static final String PREFERRED =  "preferred";*/
-
+  /**
+   * Roles to be assumed on nodes that don't have roles specified for them at startup
+   */
   public static final String DEFAULT_ROLES_STRING = "data:on,overseer:allowed";
 
   // Map of roles to mode that are applicable for this node.
@@ -60,7 +57,7 @@ public class NodeRoles {
     }
     for(Role r: Role.values()) {
       if (!roles.containsKey(r)) {
-        roles.put(r, r.defaultMode());
+        roles.put(r, r.modeWhenRoleIsAbsent());
       }
     }
     nodeRoles = Collections.unmodifiableMap(roles);
@@ -82,11 +79,11 @@ public class NodeRoles {
   public enum Mode {
     ON, OFF, ALLOWED, PREFERRED, DISALLOWED;
 
-    @Override
     /**
      * Need this lowercasing so that the ZK references use the lowercase form, which is
      * also the form documented in user facing documentation.
      */
+    @Override
     public String toString() {
       return name().toLowerCase(Locale.ROOT);
     }
@@ -99,7 +96,7 @@ public class NodeRoles {
         return Set.of(Mode.ON, Mode.OFF);
       }
       @Override
-      public Mode defaultMode() {
+      public Mode modeWhenRoleIsAbsent() {
         return Mode.OFF;
       }
     },
@@ -109,7 +106,7 @@ public class NodeRoles {
         return Set.of(Mode.ALLOWED, Mode.PREFERRED, Mode.DISALLOWED);
       }
       @Override
-      public Mode defaultMode() {
+      public Mode modeWhenRoleIsAbsent() {
         return Mode.DISALLOWED;
       }
     };
@@ -132,7 +129,7 @@ public class NodeRoles {
     /**
      * Default mode for a role in nodes where this role is not specified.
      */
-    public abstract Mode defaultMode();
+    public abstract Mode modeWhenRoleIsAbsent();
 
     @Override
     public String toString() {
diff --git a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
index 0fc0e95..fb05e34 100644
--- a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
@@ -21,7 +21,6 @@ import java.lang.invoke.MethodHandles;
 import java.util.Collection;
 import java.util.Collections;
 
-import java.util.List;
 import java.util.Map;
 
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
@@ -49,37 +48,25 @@ public class NodeRolesTest extends SolrCloudTestCase {
     shutdownCluster();
   }
 
-  @SuppressWarnings("unchecked")
+  @SuppressWarnings("rawtypes")
   public void testRoleIntegration() throws Exception {
     JettySolrRunner j0 = cluster.getJettySolrRunner(0);
-    JettySolrRunner j1, j2;
-    V2Response rsp = new V2Request.Builder("/cluster/node-roles/supported").GET().build().process(cluster.getSolrClient());
-    Map<String, Object> l = (Map<String, Object>) rsp._get("supported-roles", Collections.emptyMap());
-    assertTrue(l.containsKey("data"));
-    assertTrue(l.containsKey("overseer"));
+    testSupportedRolesAPI();
 
-    j1 = startNodeWithRoles("overseer:preferred,data:off");
+    // Start a dedicated overseer node
+    JettySolrRunner j1 = startNodeWithRoles("overseer:preferred,data:off");
+    validateNodeRoles(j1.getNodeName(), "node-roles/overseer/preferred", j1.getNodeName(), "node-roles/data/off");
 
-    rsp = new V2Request.Builder("/cluster/node-roles").GET().build().process(cluster.getSolrClient());
-    assertEquals(j1.getNodeName(), rsp._getStr("node-roles/overseer/preferred[0]", null));
-    assertEquals(j1.getNodeName(), rsp._getStr("node-roles/data/off[0]", null));
-    OverseerRolesTest.waitForNewOverseer(20, j1.getNodeName(), false);
+    V2Response rsp;
+    OverseerRolesTest.waitForNewOverseer(20, j1.getNodeName(), true);
 
-    // start another node that is overseer but has data
-    j2 = startNodeWithRoles("overseer:preferred,data:on");
-    rsp = new V2Request.Builder("/cluster/node-roles").GET().build().process(cluster.getSolrClient());
+    // Start another node that is allowed or preferred overseer but has data
+    String overseerModeOnDataNode = random().nextBoolean() ? "preferred" : "allowed";
+    JettySolrRunner j2 = startNodeWithRoles("overseer:" + overseerModeOnDataNode + ",data:on");
+    validateNodeRoles(j2.getNodeName(), "node-roles/overseer/" + overseerModeOnDataNode, j2.getNodeName(), "node-roles/data/on");
 
-    assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
-    assertTrue( ((Collection)rsp._get("node-roles/data/on", Collections.emptyList())).contains(j2.getNodeName()));
-
-    rsp = new V2Request.Builder("/cluster/node-roles/role/overseer").GET().build().process(cluster.getSolrClient());
-
-    assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
-    assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j1.getNodeName()));
-
-    rsp = new V2Request.Builder("/cluster/node-roles/role/overseer/preferred").GET().build().process(cluster.getSolrClient());
-    assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
-    assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j1.getNodeName()));
+    // validate the preferred overseers
+    validateNodeRoles(j2.getNodeName(), "node-roles/overseer/" + overseerModeOnDataNode, j1.getNodeName(), "node-roles/overseer/preferred");
 
     String COLLECTION_NAME = "TEST_ROLES";
     CollectionAdminRequest
@@ -94,8 +81,32 @@ public class NodeRolesTest extends SolrCloudTestCase {
 
     // Shutdown the dedicated overseer, make sure that node disappears from the roles output
     j1.stop();
-    rsp = new V2Request.Builder("/cluster/node-roles/role/overseer/preferred").GET().build().process(cluster.getSolrClient());
-    assertFalse (((Collection) rsp._get("node-roles/overseer/preferred" , null)). contains(j1.getNodeName()));
+
+    // Wait and make sure that another node picks up overseer responsibilities
+    OverseerRolesTest.waitForNewOverseer(20, it -> !dedicatedOverseer.equals(it), false);
+
+    // Make sure the stopped node no longer has the role assigned
+    rsp = new V2Request.Builder("/cluster/node-roles/role/overseer/" + overseerModeOnDataNode).GET().build().process(cluster.getSolrClient());
+    assertFalse(((Collection) rsp._get("node-roles/overseer/" + overseerModeOnDataNode, null)).contains(j1.getNodeName()));
+  }
+
+  @SuppressWarnings("rawtypes")
+  private void validateNodeRoles(String... nodenamePaths) throws org.apache.solr.client.solrj.SolrServerException, java.io.IOException {
+    V2Response rsp = new V2Request.Builder("/cluster/node-roles").GET().build().process(cluster.getSolrClient());
+    for (int i = 0; i < nodenamePaths.length; i += 2) {
+      String nodename = nodenamePaths[i];
+      String path = nodenamePaths[i + 1];
+      assertTrue("Didn't find " + nodename + " at " + path + ". Full response: " + rsp.jsonStr(),
+              ((Collection) rsp._get(path, Collections.emptyList())).contains(nodename));
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private void testSupportedRolesAPI() throws Exception {
+    V2Response rsp = new V2Request.Builder("/cluster/node-roles/supported").GET().build().process(cluster.getSolrClient());
+    Map<String, Object> l = (Map<String, Object>) rsp._get("supported-roles", Collections.emptyMap());
+    assertTrue(l.containsKey("data"));
+    assertTrue(l.containsKey("overseer"));
   }
 
   private JettySolrRunner startNodeWithRoles(String roles) throws Exception {
diff --git a/solr/core/src/test/org/apache/solr/cloud/OverseerRolesTest.java b/solr/core/src/test/org/apache/solr/cloud/OverseerRolesTest.java
index 508e74e..6e59f09 100644
--- a/solr/core/src/test/org/apache/solr/cloud/OverseerRolesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/OverseerRolesTest.java
@@ -54,7 +54,7 @@ public class OverseerRolesTest extends SolrCloudTestCase {
     shutdownCluster();
   }
   
-  private static void waitForNewOverseer(int seconds, Predicate<String> state, boolean failOnIntermediateTransition) throws Exception {
+  public static void waitForNewOverseer(int seconds, Predicate<String> state, boolean failOnIntermediateTransition) throws Exception {
     TimeOut timeout = new TimeOut(seconds, TimeUnit.SECONDS, TimeSource.NANO_TIME);
     String current = null;
     while (timeout.hasTimedOut() == false) {
diff --git a/solr/solr-ref-guide/src/node-roles.adoc b/solr/solr-ref-guide/src/node-roles.adoc
index 455de14..aa36fe6e3 100644
--- a/solr/solr-ref-guide/src/node-roles.adoc
+++ b/solr/solr-ref-guide/src/node-roles.adoc
@@ -93,12 +93,10 @@ curl http://localhost:8983/api/cluster/node-roles/supported
 {
   "supported-roles":{
     "data":{
-      "defaultIfAbsent":"off",
       "modes":["off",
         "on"]
     },
     "overseer":{
-      "defaultIfAbsent":"disallowed",
       "modes":["disallowed",
         "allowed",
         "preferred"]
diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java b/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java
index 8814cc9..d35bd8f 100644
--- a/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java
+++ b/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java
@@ -108,7 +108,7 @@ public class ZkStateReader implements SolrCloseable {
    * The following, node_roles and roles.json are for assigning roles to
    * nodes. The node_roles is the preferred way (using -Dsolr.node.roles param),
    * and roles.json is used by legacy ADDROLE API command.
-   * TODO: Deprecate & remove support for roles.json in an upcoming release.
+   * TODO: Deprecate and remove support for roles.json in an upcoming release.
    */
   public static final String NODE_ROLES = "/node_roles";
   public static final String ROLES = "/roles.json";

[solr] 21/28: Adding Roles API to ref guide

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 6ffba2ac5f30e428e3ed76f4de28bd320f6609d4
Author: Ishan Chattopadhyaya <is...@apache.org>
AuthorDate: Sat Dec 18 15:12:13 2021 +0530

    Adding Roles API to ref guide
---
 .../java/org/apache/solr/handler/ClusterAPI.java   | 42 ++++++++--
 .../test/org/apache/solr/cloud/NodeRolesTest.java  | 18 ++--
 solr/solr-ref-guide/src/node-roles.adoc            | 97 +++++++++++++++++++++-
 3 files changed, 140 insertions(+), 17 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index 9f93c39..39f4750 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -103,10 +103,8 @@ public class ClusterAPI {
       }
       for (String child : children) {
         Object c = readRecursive(path + "/" + child, zk, depth - 1);
-
         result.put(child, c);
       }
-
     } catch (Exception e) {
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
     }
@@ -115,7 +113,6 @@ public class ClusterAPI {
     } else {
       return result;
     }
-
   }
 
   @EndPoint(method = GET,
@@ -123,16 +120,45 @@ public class ClusterAPI {
           permission = COLL_READ_PERM)
   public void nodesWithRole(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     String role = req.getPathTemplateValues().get("role");
-    rsp.add(role,
+    rsp.add("node-roles", Map.of(role,
             readRecursive(ZkStateReader.NODE_ROLES + "/"+ role,
-                    collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager(), 2));
+                    collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager(), 2)));
+  }
+
+  @EndPoint(method = GET,
+          path = "/cluster/node-roles/nodes/{node}",
+          permission = COLL_READ_PERM)
+  @SuppressWarnings("unchecked")
+  public void rolesForNode(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+    String node = req.getPathTemplateValues().get("node");
+    Map<String, String> ret = new HashMap<String, String>();
+    Map<String, Map<String, Set<String>>> roles = (Map<String, Map<String, Set<String>>>) readRecursive(ZkStateReader.NODE_ROLES,
+            collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager(), 3);
+    for (String role: roles.keySet()) {
+      for (String mode : roles.get(role).keySet()) {
+        if (roles.get(role).get(mode) instanceof List && ((List) roles.get(role).get(mode)).size() == 0) {
+          continue;
+        }
+        Set<String> nodes = roles.get(role).get(mode);
+        if (nodes.contains(node)) ret.put(role, mode);
+      }
+    }
+    for (String role: ret.keySet()) {
+      rsp.add(role, ret.get(role));
+    }
   }
+
   @EndPoint(method = GET,
           path = "/cluster/node-roles/supported",
           permission = COLL_READ_PERM)
   public void supportedRoles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
-    rsp.add("supported-roles",
-                    collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager().listData(ZkStateReader.NODE_ROLES));
+    //rsp.add("supported-roles",
+    //                collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager().listData(ZkStateReader.NODE_ROLES));
+    Map<String, Object> roleModesSupportedMap = new HashMap<>();
+    for (NodeRoles.Role role: NodeRoles.Role.values()) {
+      roleModesSupportedMap.put(role.toString(), Map.of("modes", role.supportedModes(), "defaultIfAbsent", role.defaultIfAbsent()));
+    }
+    rsp.add("supported-roles", roleModesSupportedMap);
   }
 
   @EndPoint(method = GET,
@@ -143,7 +169,7 @@ public class ClusterAPI {
     String roleVal = req.getPathTemplateValues().get("role-val");
     List<String> nodes =  collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager()
             .getDistribStateManager().listData(ZkStateReader.NODE_ROLES + "/"+ role+"/"+roleVal);
-    rsp.add( role, Collections.singletonMap(roleVal, nodes));
+    rsp.add( "node-roles", Map.of(role, Collections.singletonMap(roleVal, nodes)));
   }
 
    public static List<String> getNodesByRole(NodeRoles.Role role, String val, DistribStateManager zk)
diff --git a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
index cda83ad..1970f6c 100644
--- a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
@@ -22,6 +22,8 @@ import java.util.Collection;
 import java.util.Collections;
 
 import java.util.List;
+import java.util.Map;
+
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.V2Request;
@@ -52,9 +54,9 @@ public class NodeRolesTest extends SolrCloudTestCase {
     JettySolrRunner j0 = cluster.getJettySolrRunner(0);
     JettySolrRunner j1 = null, j2 = null;
     V2Response rsp = new V2Request.Builder("/cluster/node-roles/supported").GET().build().process(cluster.getSolrClient());
-    List<String> l = (List<String>) rsp._get("supported-roles", Collections.emptyList());
-    assertTrue(l.contains("data"));
-    assertTrue(l.contains("overseer"));
+    Map<String, Object> l = (Map<String, Object>) rsp._get("supported-roles", Collections.emptyMap());
+    assertTrue(l.containsKey("data"));
+    assertTrue(l.containsKey("overseer"));
 
     j1 = startNodeWithRoles("overseer:preferred,data:off");
 
@@ -72,12 +74,12 @@ public class NodeRolesTest extends SolrCloudTestCase {
 
     rsp = new V2Request.Builder("/cluster/node-roles/overseer").GET().build().process(cluster.getSolrClient());
 
-    assertTrue( ((Collection)rsp._get("overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
-    assertTrue( ((Collection)rsp._get("overseer/preferred", Collections.emptyList())).contains(j1.getNodeName()));
+    assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
+    assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j1.getNodeName()));
 
     rsp = new V2Request.Builder("/cluster/node-roles/overseer/preferred").GET().build().process(cluster.getSolrClient());
-    assertTrue( ((Collection)rsp._get("overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
-    assertTrue( ((Collection)rsp._get("overseer/preferred", Collections.emptyList())).contains(j1.getNodeName()));
+    assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
+    assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j1.getNodeName()));
 
     String COLLECTION_NAME = "TEST_ROLES";
     CollectionAdminRequest
@@ -93,7 +95,7 @@ public class NodeRolesTest extends SolrCloudTestCase {
     // Shutdown the dedicated overseer, make sure that node disappears from the roles output
     j1.stop();
     rsp = new V2Request.Builder("/cluster/node-roles/overseer/preferred").GET().build().process(cluster.getSolrClient());
-    assertFalse (((Collection) rsp._get("overseer/preferred" , null)). contains(j1.getNodeName()));
+    assertFalse (((Collection) rsp._get("node-roles/overseer/preferred" , null)). contains(j1.getNodeName()));
   }
 
   private JettySolrRunner startNodeWithRoles(String roles) throws Exception {
diff --git a/solr/solr-ref-guide/src/node-roles.adoc b/solr/solr-ref-guide/src/node-roles.adoc
index f3e5969..ed2c07a 100644
--- a/solr/solr-ref-guide/src/node-roles.adoc
+++ b/solr/solr-ref-guide/src/node-roles.adoc
@@ -62,4 +62,99 @@ A common situation arises when the nodes in a cluster is under heavy querying or
 * Most nodes, say 10 of them, in the cluster start with `-Dsolr.node.roles=data:on,overseer:allowed` (or with no parameter, since the default value for `solr.node.roles` is the same).
 * One or more nodes can start with `-Dsolr.node.roles=overseer:preferred` (or `-Dsolr.node.roles=overseer:preferred,data:off`)
 
-In this arrangement, such dedicated nodes can have lesser resources than other data nodes (since these are stateless nodes) and yet the cluster will behave optimally. In case the dedicated overseer nodes go down for some reason, the overseer leader will be elected from one of the data nodes (since they have overseer in "allowed" mode), and once one of the dedicated overseer nodes are back up, it will assume the overseer leadership again.
\ No newline at end of file
+In this arrangement, such dedicated nodes can have lesser resources than other data nodes (since these are stateless nodes) and yet the cluster will behave optimally. In case the dedicated overseer nodes go down for some reason, the overseer leader will be elected from one of the data nodes (since they have overseer in "allowed" mode), and once one of the dedicated overseer nodes are back up, it will assume the overseer leadership again.
+
+== Roles API
+
+=== GET /api/cluster/node-roles/supported
+
+*Input*
+[source,text]
+----
+curl http://localhost:8983/api/cluster/node-roles/supported
+----
+
+*Output*
+[source,text]
+----
+{
+  "supported-roles":{
+    "data":{
+      "defaultIfAbsent":"off",
+      "modes":["off",
+        "on"]
+    },
+    "overseer":{
+      "defaultIfAbsent":"disallowed",
+      "modes":["disallowed",
+        "allowed",
+        "preferred"]
+    }
+  }
+}
+----
+
+=== GET /api/cluster/node-roles
+
+*Input*
+[source,text]
+----
+curl http://localhost:8983/api/cluster/node-roles
+----
+
+*Output*
+[source,text]
+----
+{
+  "node-roles":{
+    "data":{
+      "off":["solr2:8983_solr"],
+      "on":["solr1:8983_solr"]
+    },
+    "overseer":{
+      "allowed":["solr1:8983_solr"],
+      "disallowed":[],
+      "preferred":["solr2:8983_solr"]
+    }
+  }
+}
+----
+
+=== GET /api/cluster/node-roles/{role}
+
+*Input*
+[source,text]
+----
+http://localhost:8983/api/node-roles/data
+----
+
+*Output*
+[source,text]
+----
+{
+  "node-roles":{
+    "data":{
+      "off":["solr2:8983_solr"],
+      "on":["solr1:8983_solr"]
+    }
+  }
+}
+----
+
+
+=== GET /api/cluster/node-roles/nodes/{node}
+
+*Input*
+[source,text]
+----
+curl http://localhost:8983/api/cluster/node-roles/nodes/solr1:8983_solr
+----
+
+*Output*
+[source,text]
+----
+{
+  "data":"on",
+  "overseer":"allowed"
+}
+----

[solr] 25/28: rearranged API paths

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 483f9849006b995d36dfaaf6099e8756f4b9e5b9
Author: Noble Paul <no...@gmail.com>
AuthorDate: Sun Dec 19 15:24:39 2021 +1100

    rearranged API paths
---
 .../java/org/apache/solr/handler/ClusterAPI.java   | 10 ++++----
 .../test/org/apache/solr/cloud/NodeRolesTest.java  |  6 ++---
 solr/solr-ref-guide/src/node-roles.adoc            | 27 ++++++++++++++++++----
 3 files changed, 30 insertions(+), 13 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index 39f4750..6a0148e 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -116,7 +116,7 @@ public class ClusterAPI {
   }
 
   @EndPoint(method = GET,
-          path = "/cluster/node-roles/{role}",
+          path = "/cluster/node-roles/role/{role}",
           permission = COLL_READ_PERM)
   public void nodesWithRole(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     String role = req.getPathTemplateValues().get("role");
@@ -126,7 +126,7 @@ public class ClusterAPI {
   }
 
   @EndPoint(method = GET,
-          path = "/cluster/node-roles/nodes/{node}",
+          path = "/cluster/node-roles/node/{node}",
           permission = COLL_READ_PERM)
   @SuppressWarnings("unchecked")
   public void rolesForNode(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
@@ -152,9 +152,7 @@ public class ClusterAPI {
           path = "/cluster/node-roles/supported",
           permission = COLL_READ_PERM)
   public void supportedRoles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
-    //rsp.add("supported-roles",
-    //                collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager().listData(ZkStateReader.NODE_ROLES));
-    Map<String, Object> roleModesSupportedMap = new HashMap<>();
+     Map<String, Object> roleModesSupportedMap = new HashMap<>();
     for (NodeRoles.Role role: NodeRoles.Role.values()) {
       roleModesSupportedMap.put(role.toString(), Map.of("modes", role.supportedModes(), "defaultIfAbsent", role.defaultIfAbsent()));
     }
@@ -162,7 +160,7 @@ public class ClusterAPI {
   }
 
   @EndPoint(method = GET,
-          path = "/cluster/node-roles/{role}/{role-val}",
+          path = "/cluster/node-roles/role/{role}/{role-val}",
           permission = COLL_READ_PERM)
   public void nodesWithRoleVal(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     String role = req.getPathTemplateValues().get("role");
diff --git a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
index 1970f6c..63f7d86 100644
--- a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
@@ -72,12 +72,12 @@ public class NodeRolesTest extends SolrCloudTestCase {
     assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
     assertTrue( ((Collection)rsp._get("node-roles/data/on", Collections.emptyList())).contains(j2.getNodeName()));
 
-    rsp = new V2Request.Builder("/cluster/node-roles/overseer").GET().build().process(cluster.getSolrClient());
+    rsp = new V2Request.Builder("/cluster/node-roles/role/overseer").GET().build().process(cluster.getSolrClient());
 
     assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
     assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j1.getNodeName()));
 
-    rsp = new V2Request.Builder("/cluster/node-roles/overseer/preferred").GET().build().process(cluster.getSolrClient());
+    rsp = new V2Request.Builder("/cluster/node-roles/role/overseer/preferred").GET().build().process(cluster.getSolrClient());
     assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
     assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j1.getNodeName()));
 
@@ -94,7 +94,7 @@ public class NodeRolesTest extends SolrCloudTestCase {
 
     // Shutdown the dedicated overseer, make sure that node disappears from the roles output
     j1.stop();
-    rsp = new V2Request.Builder("/cluster/node-roles/overseer/preferred").GET().build().process(cluster.getSolrClient());
+    rsp = new V2Request.Builder("/cluster/node-roles/role/overseer/preferred").GET().build().process(cluster.getSolrClient());
     assertFalse (((Collection) rsp._get("node-roles/overseer/preferred" , null)). contains(j1.getNodeName()));
   }
 
diff --git a/solr/solr-ref-guide/src/node-roles.adoc b/solr/solr-ref-guide/src/node-roles.adoc
index 3e902fa..e5a70ec 100644
--- a/solr/solr-ref-guide/src/node-roles.adoc
+++ b/solr/solr-ref-guide/src/node-roles.adoc
@@ -135,14 +135,14 @@ curl http://localhost:8983/api/cluster/node-roles
 }
 ----
 
-=== GET /api/cluster/node-roles/{role}
+=== GET /api/cluster/node-roles/role/{role}
 
 Fetches the current node roles assignment for a specified role.
 
 *Input*
 [source,text]
 ----
-http://localhost:8983/api/node-roles/data
+http://localhost:8983/api/node-roles/role/data
 ----
 
 *Output*
@@ -158,15 +158,34 @@ http://localhost:8983/api/node-roles/data
 }
 ----
 
+*Input*
+[source,text]
+----
+http://localhost:8983/api/node-roles/role/data/off
+----
+
+*Output*
+[source,text]
+----
+{
+  "node-roles":{
+    "data":{
+      "off":["solr2:8983_solr"]
+    }
+  }
+}
+----
+
+
 
-=== GET /api/cluster/node-roles/nodes/{node}
+=== GET /api/cluster/node-roles/node/{node}
 
 Fetches the current node roles assignment for the specified node.
 
 *Input*
 [source,text]
 ----
-curl http://localhost:8983/api/cluster/node-roles/nodes/solr1:8983_solr
+curl http://localhost:8983/api/cluster/node-roles/node/solr1:8983_solr
 ----
 
 *Output*

[solr] 24/28: Fix typo in reg guide

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 7ec11d51084da2330a22cb1858c6b87c372fc64f
Author: Ishan Chattopadhyaya <is...@apache.org>
AuthorDate: Mon Dec 20 16:59:13 2021 +0530

    Fix typo in reg guide
---
 solr/solr-ref-guide/src/node-roles.adoc | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/solr/solr-ref-guide/src/node-roles.adoc b/solr/solr-ref-guide/src/node-roles.adoc
index 19c5beb..3e902fa 100644
--- a/solr/solr-ref-guide/src/node-roles.adoc
+++ b/solr/solr-ref-guide/src/node-roles.adoc
@@ -45,7 +45,7 @@ Examples: `-Dsolr.node.roles=data:on,overseer:allowed` or `-Dsolr.node.roles=ove
 
 [TIP]
 ====
-If a node has been started with no `solr.node.roles` parameter, it will be assumed to have the data role turned on and overseer role allowed on it. If you've never needed roles before, you likely won't need to change anything in your startup parameters to accomodate the functionality associated with these roles.
+If a node has been started with no `solr.node.roles` parameter, it will be assumed to have the data role turned on and overseer role allowed on it. If you've never used roles before, you likely won't need to change anything in your startup parameters to accomodate the functionality associated with these roles.
 ====
 
 .Supported roles
@@ -68,12 +68,12 @@ A node with this role can perform duties of an overseer node (unless mode is `di
 
 == Example usage
 
-Sometimes, when the nodes in a cluster is under heavy querying or indexing load, the overseer leader node is unable to perform collection management duties efficiently. It might be reasonable to have dedicated nodes to act as the overseer. Such an effect can be achieved as follows:
+Sometimes, when the nodes in a cluster are under heavy querying or indexing load, the overseer leader node might be unable to perform collection management duties efficiently. It might be reasonable to have dedicated nodes to act as the overseer. Such an effect can be achieved as follows:
 
-* Most nodes, say 10 of them, in the cluster start with `-Dsolr.node.roles=data:on,overseer:allowed` (or with no parameter, since the default value for `solr.node.roles` is the same).
-* One or more nodes can start with `-Dsolr.node.roles=overseer:preferred` (or `-Dsolr.node.roles=overseer:preferred,data:off`)
+* Most nodes (data nodes) in the cluster start with `-Dsolr.node.roles=data:on,overseer:allowed` (or with no parameter, since the default value for `solr.node.roles` is the same).
+* One or more nodes (dedicated overseer nodes) can start with `-Dsolr.node.roles=overseer:preferred` (or `-Dsolr.node.roles=overseer:preferred,data:off`)
 
-In this arrangement, such dedicated nodes can have lesser resources than other data nodes (since these are stateless nodes) and yet the cluster will behave optimally. In case the dedicated overseer nodes go down for some reason, the overseer leader will be elected from one of the data nodes (since they have overseer in "allowed" mode), and once one of the dedicated overseer nodes are back up, it will assume the overseer leadership again.
+In this arrangement, such dedicated nodes can be provisioned on hardware with lesser resources like CPU, memory or disk space than other data nodes (since these are stateless nodes) and yet the cluster will behave optimally. In case the dedicated overseer nodes go down for some reason, the overseer leader will be elected from one of the data nodes (since they have overseer in "allowed" mode), and once one of the dedicated overseer nodes are back up again, it will be re-elected for the ov [...]
 
 == Roles API
 

[solr] 18/28: removed unnecessary changes

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 525ff87d6cc39362528d7e4bcc0e0876699777f1
Author: Noble Paul <no...@gmail.com>
AuthorDate: Fri Dec 17 16:37:59 2021 +1100

    removed unnecessary changes
---
 solr/core/src/java/org/apache/solr/cloud/ZkController.java | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index 33ea43d..a249e4f 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -685,9 +685,7 @@ public class ZkController implements Closeable {
         } finally {
 
           // just in case the OverseerElectionContext managed to start another Overseer
-          if(overseer != null) {
-            IOUtils.closeQuietly(overseer);
-          }
+          IOUtils.closeQuietly(overseer);
 
           ExecutorUtil.shutdownAndAwaitTermination(customThreadPool);
         }
@@ -913,7 +911,7 @@ public class ZkController implements Closeable {
       registerLiveNodesListener();
 
       // start the overseer first as following code may need it's processing
-      if (!zkRunOnly &&  cc.nodeRoles.isOverseerAllowed()) {
+      if (!zkRunOnly) {
         overseerElector = new LeaderElector(zkClient);
         this.overseer = new Overseer((HttpShardHandler) cc.getShardHandlerFactory().getShardHandler(), cc.getUpdateShardHandler(),
             CommonParams.CORES_HANDLER_PATH, zkStateReader, this, cloudConfig);

[solr] 05/28: use a separate node

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 16e3ec8cd8e31f5b05fcef0daa3feda4e2893f95
Author: Noble Paul <no...@gmail.com>
AuthorDate: Tue Nov 16 15:58:02 2021 +1100

    use a separate node
---
 .../java/org/apache/solr/cloud/ZkController.java   | 56 ++++++++---------
 .../impl/SimpleClusterAbstractionsImpl.java        | 59 +++++++++++-------
 .../src/java/org/apache/solr/core/NodeRole.java    | 70 ++++------------------
 .../test/org/apache/solr/core/TestNodeRole.java    | 69 ---------------------
 .../apache/solr/common/cloud/ZkStateReader.java    |  1 +
 5 files changed, 76 insertions(+), 179 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index 48c26a4..00274aa 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -73,7 +73,13 @@ import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.URLUtil;
 import org.apache.solr.common.util.Utils;
-import org.apache.solr.core.*;
+import org.apache.solr.core.CloseHook;
+import org.apache.solr.core.CloudConfig;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.CoreDescriptor;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.core.SolrCoreInitializationException;
+import org.apache.solr.core.NodeRole;
 import org.apache.solr.handler.component.HttpShardHandler;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -100,7 +106,6 @@ import static org.apache.solr.common.cloud.ZkStateReader.NODE_NAME_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.REJOIN_AT_HEAD_PROP;
 import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
 import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDROLE;
-import static org.apache.solr.common.params.CoreAdminParams.ACTION;
 import static org.apache.zookeeper.ZooDefs.Ids.OPEN_ACL_UNSAFE;
 
 /**
@@ -461,7 +466,6 @@ public class ZkController implements Closeable {
     });
 
     init();
-    persistRoles();
 
     if (distributedClusterStateUpdater.isDistributedStateUpdate()) {
       this.overseerJobQueue = null;
@@ -476,28 +480,6 @@ public class ZkController implements Closeable {
     assert ObjectReleaseTracker.track(this);
   }
 
-  @SuppressWarnings("unchecked")
-  private void persistRoles() throws InterruptedException {
-    try {
-      zkClient.atomicUpdate(ZkStateReader.ROLES,
-              bytes -> {
-                Map<String, Object> map = bytes == null ? null :
-                        (Map<String, Object>) Utils.fromJSON(bytes);
-                map = cc.nodeRole.modifyRoleData(map,
-                        getNodeName());
-                return map == null ? null : Utils.toJSON(map);
-              });
-      if (cc.nodeRole.role() == NodeRole.Type.overseer) {
-        getOverseerCollectionQueue()
-                .offer(Utils.toJSON(Utils.makeMap(ACTION, ADDROLE.name(),
-                        "role", NodeRole.Type.overseer,
-                        "node", getNodeName())));
-      }
-    } catch (KeeperException e) {
-      throw new SolrException(ErrorCode.SERVER_ERROR, "can't update roles");
-    }
-  }
-
   /**
    * <p>Verifies if /clusterstate.json exists in Zookeepeer, and if it does and is not empty, refuses to start and outputs
    * a helpful message regarding collection migration.</p>
@@ -873,6 +855,7 @@ public class ZkController implements Closeable {
       throws KeeperException, InterruptedException, IOException {
     ZkCmdExecutor cmdExecutor = new ZkCmdExecutor(zkClient.getZkClientTimeout());
     cmdExecutor.ensureExists(ZkStateReader.LIVE_NODES_ZKNODE, zkClient);
+    cmdExecutor.ensureExists(ZkStateReader.NODE_ROLES, zkClient);
     cmdExecutor.ensureExists(ZkStateReader.COLLECTIONS_ZKNODE, zkClient);
     cmdExecutor.ensureExists(ZkStateReader.ALIASES, zkClient);
     byte[] emptyJson = "{}".getBytes(StandardCharsets.UTF_8);
@@ -1105,6 +1088,11 @@ public class ZkController implements Closeable {
     log.info("Register node as live in ZooKeeper:{}", nodePath);
     List<Op> ops = new ArrayList<>(2);
     ops.add(Op.create(nodePath, null, zkClient.getZkACLProvider().getACLsToAdd(nodePath), CreateMode.EPHEMERAL));
+    if(cc.nodeRole.role() != NodeRole.Type.data) {
+      //this is a non-data node
+      ops.add(Op.create(ZkStateReader.NODE_ROLES + "/" + nodeName, Utils.toJSON(cc.nodeRole), zkClient.getZkACLProvider().getACLsToAdd(nodePath), CreateMode.EPHEMERAL));
+
+    }
     zkClient.multi(ops, true);
   }
 
@@ -2210,6 +2198,10 @@ public class ZkController implements Closeable {
 
   public void checkOverseerDesignate() {
     try {
+      if(cc.nodeRole.role() == NodeRole.Type.overseer) {
+        setPreferredOverseer();
+        return;
+      }
       byte[] data = zkClient.getData(ZkStateReader.ROLES, null, new Stat(), true);
       if (data == null) return;
       Map<?,?> roles = (Map<?,?>) Utils.fromJSON(data);
@@ -2217,11 +2209,7 @@ public class ZkController implements Closeable {
       List<?> nodeList = (List<?>) roles.get("overseer");
       if (nodeList == null) return;
       if (nodeList.contains(getNodeName())) {
-        ZkNodeProps props = new ZkNodeProps(Overseer.QUEUE_OPERATION, ADDROLE.toString().toLowerCase(Locale.ROOT),
-            "node", getNodeName(),
-            "role", "overseer");
-        log.info("Going to add role {} ", props);
-        getOverseerCollectionQueue().offer(Utils.toJSON(props));
+       setPreferredOverseer();
       }
     } catch (NoNodeException nne) {
       return;
@@ -2230,6 +2218,14 @@ public class ZkController implements Closeable {
     }
   }
 
+  private void setPreferredOverseer() throws KeeperException, InterruptedException {
+    ZkNodeProps props = new ZkNodeProps(Overseer.QUEUE_OPERATION, ADDROLE.toString().toLowerCase(Locale.ROOT),
+        "node", getNodeName(),
+        "role", "overseer");
+    log.info("Going to add role {} ", props);
+    getOverseerCollectionQueue().offer(Utils.toJSON(props));
+  }
+
   public CoreContainer getCoreContainer() {
     return cc;
   }
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
index d7453d6..f403aed 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
@@ -33,7 +33,6 @@ import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.CollectionAdminParams;
 import org.apache.solr.common.util.Pair;
 import org.apache.solr.common.util.Utils;
-import org.apache.solr.core.NodeRole;
 import org.apache.zookeeper.KeeperException;
 
 import javax.annotation.Nonnull;
@@ -61,27 +60,8 @@ class SimpleClusterAbstractionsImpl {
     private final Set<Node> liveNodes;
     private final ClusterState clusterState;
 
-    @SuppressWarnings("unchecked")
     ClusterImpl(SolrCloudManager solrCloudManager) throws IOException {
-      Set<String> liveNodes = solrCloudManager.getClusterStateProvider().getLiveNodes();
-      try {
-        if (solrCloudManager.getDistribStateManager().hasData(ZkStateReader.ROLES)) {
-          VersionedData rolesData = solrCloudManager.getDistribStateManager().getData(ZkStateReader.ROLES);
-          if (rolesData != null && rolesData.getData().length > 0) {
-            Map<String, Object> roles = (Map<String, Object>) Utils.fromJSON(rolesData.getData());
-            List<String> noReplicasNodes = (List<String>) roles.get(NodeRole.NO_REPLICAS);
-            if (noReplicasNodes != null && !noReplicasNodes.isEmpty()) {
-              liveNodes = new HashSet<>(liveNodes);
-              liveNodes.removeAll(noReplicasNodes);
-            }
-          }
-        }
-
-      } catch (NoSuchElementException nsee) {
-        //no problem
-      } catch (Exception e) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to communicate with Zookeeper");
-      }
+      Set<String> liveNodes = filterNonDataNodes(solrCloudManager);
       this.liveNodes = NodeImpl.getNodes(liveNodes);
       clusterState = solrCloudManager.getClusterStateProvider().getClusterState();
     }
@@ -108,6 +88,43 @@ class SimpleClusterAbstractionsImpl {
     }
   }
 
+  private static Set<String> filterNonDataNodes(SolrCloudManager solrCloudManager) {
+    Set<String> liveNodes = solrCloudManager.getClusterStateProvider().getLiveNodes();
+    Set<String> liveNodesCopy = liveNodes;
+    try {
+      List<String> nodesWithRoles =  solrCloudManager.getDistribStateManager().listData(ZkStateReader.NODE_ROLES);
+      if(!nodesWithRoles.isEmpty()) {
+        for (String node : nodesWithRoles) {
+          VersionedData data = null;
+          try {
+            data = solrCloudManager.getDistribStateManager()
+                    .getData(ZkStateReader.NODE_ROLES + "/" + node);
+          } catch (NoSuchElementException e) {
+            //this node probably went down
+            continue;
+          }
+          if(data != null && data.getData() != null && data.getData().length >0) {
+            @SuppressWarnings("unchecked")
+            Map<String,Object> map = (Map<String, Object>) Utils.fromJSON(data.getData());
+            if(Boolean.FALSE.equals(map.get("hasData"))) {
+              if(liveNodesCopy == liveNodes) {
+                //the map provided should not be modified. So we make a copy
+                liveNodesCopy = new HashSet<>(liveNodes);
+              }
+              liveNodesCopy.remove(node);
+            }
+          }
+        }
+      }
+
+    } catch (NoSuchElementException nsee) {
+      //no problem
+    } catch (Exception e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to communicate with Zookeeper");
+    }
+    return liveNodesCopy;
+  }
+
 
   static class NodeImpl implements Node {
     public final String nodeName;
diff --git a/solr/core/src/java/org/apache/solr/core/NodeRole.java b/solr/core/src/java/org/apache/solr/core/NodeRole.java
index 1de7832..7feb742 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeRole.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeRole.java
@@ -16,19 +16,18 @@
  */
 package org.apache.solr.core;
 
-import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
 import java.util.*;
+import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.StringUtils;
 import org.apache.solr.common.util.StrUtils;
 
-public class NodeRole {
-  private boolean hasData;
-  private Type role;
+public class NodeRole implements MapWriter {
+  private boolean hasData = true;
+  private Type role  =Type.data;
 
   public NodeRole(String role) {
     if (StringUtils.isEmpty(role)) {
-      hasData = true;
-      this.role = Type.data;
       return;
     }
     Set<String> roles = new HashSet<>(StrUtils.split(role, ','));
@@ -58,65 +57,18 @@ public class NodeRole {
     return role;
   }
 
-  @SuppressWarnings("unchecked")
-  public Map<String, Object> modifyRoleData(Map<String, Object> rolesData, String nodeName) {
-    if (this.role == Type.data) {
-      //this is a normal data node, nothing to do
-      if (rolesData == null) return null;// no role info, return
-      if (removeStaleRoles(nodeName, rolesData, Collections.emptySet())) {
-        return rolesData;
-      } else {
-        return null;
-      }
-    } else {
-      if (rolesData == null) rolesData = new HashMap<>();
-      List<String> nodes = (List<String>) rolesData.computeIfAbsent(role.name(), s -> new ArrayList<String>());
-      boolean isModified = false;
-      if (!nodes.contains(nodeName)) {
-        nodes.add(nodeName);
-        isModified = true;
-      }
-      List<String> nonReplicaNodes = (List<String>) rolesData.computeIfAbsent(NO_REPLICAS, s -> new ArrayList<String>());
-      if (hasData) {
-        if(nonReplicaNodes.contains(nodeName)) {
-          nonReplicaNodes.remove(nodeName);
-          isModified = true;
-        }
-      } else {
-        if(!nonReplicaNodes.contains(nodeName)){
-          nonReplicaNodes.add(nodeName);
-          isModified = true;
-        }
-      }
-      if (isModified || removeStaleRoles(nodeName, rolesData, ImmutableSet.of(role.name(), NO_REPLICAS))) {
-        return rolesData;
-      } else {
-        return null;
-      }
-    }
-
-  }
-
-  @SuppressWarnings("unchecked")
-  private boolean removeStaleRoles(String nodeName, Map<String, Object> rolesData, Set<String> ignore) {
-    boolean[] isModified = new boolean[]{false};
-    rolesData.forEach((s, o) -> {
-      if (ignore.contains(s)) return;
-      if (o instanceof List) {
-        List<String> list = (List<String>) o;
-        if (list.contains(nodeName)) {
-          isModified[0] = true;
-          list.remove(nodeName);
-        }
-      }
-    });
-    return isModified[0];
+  @Override
+  public void writeMap(EntryWriter ew) throws IOException {
+    ew.put("hasData", hasData);
+    ew.put("role", role.name());
   }
 
   public enum Type {
     data, overseer;
   }
 
+
+
   public static final String NO_REPLICAS = "no-replicas";
 
 }
diff --git a/solr/core/src/test/org/apache/solr/core/TestNodeRole.java b/solr/core/src/test/org/apache/solr/core/TestNodeRole.java
deleted file mode 100644
index b328983..0000000
--- a/solr/core/src/test/org/apache/solr/core/TestNodeRole.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.core;
-
-import java.lang.invoke.MethodHandles;
-import java.util.Collection;
-import java.util.Map;
-import org.apache.solr.SolrTestCase;
-import org.apache.solr.common.util.Utils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class TestNodeRole extends SolrTestCase {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-
-  @SuppressWarnings("rawtypes")
-  public void testZkData() {
-    //empty
-    Map<String,Object> rolesData = null;
-    //start a node with overseer role
-    rolesData = new NodeRole("overseer").modifyRoleData(rolesData, "node1");
-    assertEquals("node1", _val(rolesData,  "overseer[0]"));
-    assertEquals("node1", _val(rolesData,  "no-replicas[0]"));
-    //now start another node with overseer
-    rolesData = new NodeRole("overseer").modifyRoleData(rolesData, "node2");
-    assertEquals("node2", _val(rolesData,  "overseer[1]"));
-    assertEquals("node2", _val(rolesData,  "no-replicas[1]"));
-    //now start another node with overseer,data
-    rolesData = new NodeRole("overseer,data").modifyRoleData(rolesData, "node3");
-
-    assertEquals("node3", _val(rolesData, "overseer[2]"));
-    assertEquals(2, ((Collection) _val(rolesData, "no-replicas")).size() );
-    //now restart node2 with no role
-
-    rolesData = new NodeRole(null).modifyRoleData(rolesData, "node2");
-    assertEquals("node3", _val(rolesData, "overseer[1]"));
-    assertEquals(1, ((Collection) _val(rolesData, "no-replicas")).size() );
-    //now restart node1 with the original values
-    assertNull(new NodeRole("overseer").modifyRoleData(rolesData, "node1"));
-
-    assertEquals("node1", _val(rolesData,  "overseer[0]"));
-    assertEquals("node1", _val(rolesData,  "no-replicas[0]"));
-    //now restart node1 with overseer,data
-    rolesData =  new NodeRole("overseer,data").modifyRoleData(rolesData, "node1");
-    assertEquals("node1", _val(rolesData,  "overseer[0]"));
-    assertEquals(0, ((Collection) _val(rolesData, "no-replicas")).size() );
-
-    log.info("rolesdata : {}", Utils.toJSONString(rolesData));
-  }
-
-  private Object _val(Map<String, Object> rolesData, String path) {
-    return Utils.getObjectByPath(rolesData, false, path);
-  }
-}
diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java b/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java
index 461dbaf..a01cab2 100644
--- a/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java
+++ b/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java
@@ -103,6 +103,7 @@ public class ZkStateReader implements SolrCloseable {
   public static final String STATE_TIMESTAMP_PROP = "stateTimestamp";
   public static final String COLLECTIONS_ZKNODE = "/collections";
   public static final String LIVE_NODES_ZKNODE = "/live_nodes";
+  public static final String NODE_ROLES = "/node_roles";
   public static final String ALIASES = "/aliases.json";
   /**
    * This ZooKeeper file is no longer used starting with Solr 9 but keeping the name around to check if it

[solr] 13/28: Adding defaultIfAbsent, minor formatting fixes

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 072f65909e7fc1e9bde4500af537b1e54ff7c2aa
Author: Ishan Chattopadhyaya <is...@apache.org>
AuthorDate: Fri Dec 10 13:28:45 2021 +0530

    Adding defaultIfAbsent, minor formatting fixes
---
 .../java/org/apache/solr/cloud/ZkController.java   |  4 +-
 .../java/org/apache/solr/core/CoreContainer.java   |  2 +-
 .../src/java/org/apache/solr/core/NodeRoles.java   | 89 +++++++++++++---------
 .../test/org/apache/solr/cloud/NodeRolesTest.java  |  9 +--
 4 files changed, 56 insertions(+), 48 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index e7b15bf..5e71098 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -852,7 +852,7 @@ public class ZkController implements Closeable {
     cmdExecutor.ensureExists(ZkStateReader.NODE_ROLES, zkClient);
     for (NodeRoles.Role role : NodeRoles.Role.values()) {
       cmdExecutor.ensureExists(ZkStateReader.NODE_ROLES + "/" + role.roleName, zkClient);
-      for (String v : role.supportedVals()) {
+      for (String v : role.supportedModes()) {
         cmdExecutor.ensureExists(ZkStateReader.NODE_ROLES + "/" + role.roleName + "/"+v, zkClient);
       }
     }
@@ -1091,7 +1091,7 @@ public class ZkController implements Closeable {
     ops.add(Op.create(nodePath, null, zkClient.getZkACLProvider().getACLsToAdd(nodePath), CreateMode.EPHEMERAL));
 
     // Create the roles node as well
-   cc.nodeRoles.ROLES.forEach((role, val) -> ops.add(Op.create(ZkStateReader.NODE_ROLES + "/" + role.roleName+ "/"+val +"/"+ nodeName,
+   cc.nodeRoles.getRoles().forEach((role, val) -> ops.add(Op.create(ZkStateReader.NODE_ROLES + "/" + role.roleName+ "/"+val +"/"+ nodeName,
            null, zkClient.getZkACLProvider().getACLsToAdd(nodePath), CreateMode.EPHEMERAL)));
 
     zkClient.multi(ops, true);
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index f7e9a1f..72a0df3 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -947,7 +947,7 @@ public class CoreContainer {
       });
 
       clusterSingletons.setReady();
-      if (NodeRoles.PREFERRED.equals(nodeRoles.getRoleVal(NodeRoles.Role.OVERSEER))) {
+      if (NodeRoles.PREFERRED.equals(nodeRoles.getRoleMode(NodeRoles.Role.OVERSEER))) {
         try {
           log.info("This node is started as a preferred overseer");
           zkSys.getZkController().setPreferredOverseer();
diff --git a/solr/core/src/java/org/apache/solr/core/NodeRoles.java b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
index fb3f011..b72e5ec 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeRoles.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeRoles.java
@@ -30,46 +30,65 @@ public class NodeRoles implements MapWriter {
 
   public static final String NODE_ROLES_PROP = "solr.node.roles";
 
-  public Map<Role, String> ROLES ;
+  public static final String ON =  "on";
+  public static final String OFF =  "off";
+  public static final String ALLOWED =  "allowed";
+  public static final String DISALLOWED =  "disallowed";
+  public static final String PREFERRED =  "preferred";
 
-  public NodeRoles(String role) {
-    ROLES = new EnumMap<>(Role.class);
-    if (StringUtils.isEmpty(role)) {
-     role = DEFAULT_ROLE;
+  public static final String DEFAULT_ROLES_STRING = "data:on,overseer:allowed";
+
+  // Map of roles to mode that are applicable for this node.
+  private Map<Role, String> nodeRoles;
+
+  public NodeRoles(String rolesString) {
+    Map<Role, String> roles = new EnumMap<>(Role.class);
+    if (StringUtils.isEmpty(rolesString)) {
+     rolesString = DEFAULT_ROLES_STRING;
     }
-    List<String> rolesList = StrUtils.splitSmart(role, ',');
-    for (String s : rolesList) {
-      List<String> roleVal =  StrUtils.splitSmart(s,':');
-      Role r = Role.getRole(roleVal.get(0));
-      if(r.supportedVals().contains(roleVal.get(1))) {
-        ROLES.put(r, roleVal.get(1));
+    List<String> rolesList = StrUtils.splitSmart(rolesString, ',');
+    for (String s: rolesList) {
+      List<String> roleMode =  StrUtils.splitSmart(s,':');
+      Role r = Role.getRole(roleMode.get(0));
+      if (r.supportedModes().contains(roleMode.get(1))) {
+        roles.put(r, roleMode.get(1));
       } else {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "UNKNOWN role value :"+roleVal.get(0));
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown role mode: " + roleMode.get(0));
       }
     }
-    ROLES = Collections.unmodifiableMap(ROLES);
+    for(Role r: Role.values()) {
+      if (!roles.containsKey(r)) {
+        roles.put(r, r.defaultIfAbsent());
+      }
+    }
+    nodeRoles = Collections.unmodifiableMap(roles);
   }
 
-  public String getRoleVal(Role role) {
-    return ROLES.get(role);
+  public Map<Role, String> getRoles() {
+    return nodeRoles;
   }
 
-
+  public String getRoleMode(Role role) {
+    return nodeRoles.get(role);
+  }
 
   @Override
   public void writeMap(EntryWriter ew) {
-    ROLES.forEach((role, s) -> ew.putNoEx(role.roleName, s));
+    nodeRoles.forEach((role, s) -> ew.putNoEx(role.roleName, s));
   }
 
   public enum Role {
-    DATA("data") ,
+    DATA("data"),
     OVERSEER("overseer") {
       @Override
-      public Set<String> supportedVals() {
-        return OVERSEER_VALS;
+      public Set<String> supportedModes() {
+        return Set.of(ALLOWED, PREFERRED, DISALLOWED);
+      }
+      @Override
+      public String defaultIfAbsent() {
+        return DISALLOWED;
       }
-    },
-    COORDINATOR("coordinator");
+    };
 
     public final String roleName;
 
@@ -78,16 +97,21 @@ public class NodeRoles implements MapWriter {
     }
 
     public static Role getRole(String value) {
-      // Given a user string "overseer", convert to OVERSEER and return the enum value
-      for (Role role : Role.values()) {
-        if(value.equals(role.roleName)) return role;
+      for (Role role: Role.values()) {
+        if (value.equals(role.roleName)) return role;
       }
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown role : "+value);
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown role: " + value);
     }
 
-    public Set<String> supportedVals() {
-      return ON_OFF;
+    public Set<String> supportedModes() {
+      return Set.of(ON, OFF);
+    }
 
+    /**
+     * Default mode for a role in nodes where this role is not specified.
+     */
+    public String defaultIfAbsent() {
+      return OFF;
     }
 
     @Override
@@ -95,13 +119,4 @@ public class NodeRoles implements MapWriter {
       return super.toString().toLowerCase();
     }
   }
-
-  public static final String ON =  "on";
-  public static final String OFF =  "off";
-  public static final Set<String> ON_OFF = Set.of(ON,OFF);
-  public static final String ALLOWED =  "allowed";
-  public static final String DISALLOWED =  "disallowed";
-  public static final String PREFERRED =  "preferred";
-  public static final Set<String> OVERSEER_VALS = Set.of(ALLOWED, DISALLOWED, PREFERRED);
-  public static final String DEFAULT_ROLE = "data:on,overseer:allowed";
 }
diff --git a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
index 18a16fe..a2e3651 100644
--- a/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/NodeRolesTest.java
@@ -48,7 +48,6 @@ public class NodeRolesTest extends SolrCloudTestCase {
 
   @SuppressWarnings("unchecked")
   public void testRoleIntegration() throws Exception {
-
     JettySolrRunner j0 = cluster.getJettySolrRunner(0);
     JettySolrRunner j1 = null, j2 = null;
     j1 = startNodeWithRoles("overseer:preferred,data:off");
@@ -58,19 +57,15 @@ public class NodeRolesTest extends SolrCloudTestCase {
     assertEquals(j1.getNodeName(), rsp._getStr("node-roles/data/off[0]", null));
     OverseerRolesTest.waitForNewOverseer(20, j1.getNodeName(), false);
 
-    //start another node that is overseer but has data
+    // start another node that is overseer but has data
     j2 = startNodeWithRoles("overseer:preferred,data:on");
     rsp = new V2Request.Builder("/cluster/node-roles").GET().build().process(cluster.getSolrClient());
 
-
     assertTrue( ((Collection)rsp._get("node-roles/overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
     assertTrue( ((Collection)rsp._get("node-roles/data/on", Collections.emptyList())).contains(j2.getNodeName()));
 
-
-
     rsp = new V2Request.Builder("/cluster/node-roles/overseer").GET().build().process(cluster.getSolrClient());
 
-
     assertTrue( ((Collection)rsp._get("overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
     assertTrue( ((Collection)rsp._get("overseer/preferred", Collections.emptyList())).contains(j1.getNodeName()));
 
@@ -78,8 +73,6 @@ public class NodeRolesTest extends SolrCloudTestCase {
     assertTrue( ((Collection)rsp._get("overseer/preferred", Collections.emptyList())).contains(j2.getNodeName()));
     assertTrue( ((Collection)rsp._get("overseer/preferred", Collections.emptyList())).contains(j1.getNodeName()));
 
-
-
     String COLLECTION_NAME = "TEST_ROLES";
     CollectionAdminRequest
             .createCollection(COLLECTION_NAME, "conf", 3, 1)

[solr] 06/28: added APIs

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 75d841055857c49aad8db976919b56da9a6c6a35
Author: Noble Paul <no...@gmail.com>
AuthorDate: Wed Nov 17 13:39:38 2021 +1100

    added APIs
---
 .../impl/SimpleClusterAbstractionsImpl.java        | 45 ++++++++--------------
 .../java/org/apache/solr/handler/ClusterAPI.java   | 40 ++++++++++++++++++-
 .../client/solrj/cloud/DistribStateManager.java    | 23 +++++++++++
 3 files changed, 78 insertions(+), 30 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
index f403aed..16ce53c 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java
@@ -19,6 +19,8 @@ package org.apache.solr.cluster.placement.impl;
 
 import java.io.IOException;
 import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 
 import com.google.common.collect.Maps;
@@ -90,39 +92,26 @@ class SimpleClusterAbstractionsImpl {
 
   private static Set<String> filterNonDataNodes(SolrCloudManager solrCloudManager) {
     Set<String> liveNodes = solrCloudManager.getClusterStateProvider().getLiveNodes();
-    Set<String> liveNodesCopy = liveNodes;
+    AtomicReference<Set<String>> liveNodesCopy = new AtomicReference<>(liveNodes);
     try {
-      List<String> nodesWithRoles =  solrCloudManager.getDistribStateManager().listData(ZkStateReader.NODE_ROLES);
-      if(!nodesWithRoles.isEmpty()) {
-        for (String node : nodesWithRoles) {
-          VersionedData data = null;
-          try {
-            data = solrCloudManager.getDistribStateManager()
-                    .getData(ZkStateReader.NODE_ROLES + "/" + node);
-          } catch (NoSuchElementException e) {
-            //this node probably went down
-            continue;
-          }
-          if(data != null && data.getData() != null && data.getData().length >0) {
-            @SuppressWarnings("unchecked")
-            Map<String,Object> map = (Map<String, Object>) Utils.fromJSON(data.getData());
-            if(Boolean.FALSE.equals(map.get("hasData"))) {
-              if(liveNodesCopy == liveNodes) {
-                //the map provided should not be modified. So we make a copy
-                liveNodesCopy = new HashSet<>(liveNodes);
-              }
-              liveNodesCopy.remove(node);
-            }
-          }
-        }
-      }
+     solrCloudManager.getDistribStateManager().forEachChild(ZkStateReader.NODE_ROLES, (name, data) -> {
+       if(data != null && data.getData() != null && data.getData().length >0) {
+         @SuppressWarnings("unchecked")
+         Map<String,Object> map = (Map<String, Object>) Utils.fromJSON(data.getData());
+         if(Boolean.FALSE.equals(map.get("hasData"))) {
+           if(liveNodesCopy.get() == liveNodes) {
+             //the map provided should not be modified. So we make a copy
+             liveNodesCopy.set(new HashSet<>(liveNodes));
+           }
+           liveNodesCopy.get().remove(name);
+         }
+       }
+     });
 
-    } catch (NoSuchElementException nsee) {
-      //no problem
     } catch (Exception e) {
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to communicate with Zookeeper");
     }
-    return liveNodesCopy;
+    return liveNodesCopy.get();
   }
 
 
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index be5c7de..6f6773d 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -17,8 +17,7 @@
 
 package org.apache.solr.handler;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 import com.google.common.collect.Maps;
 import org.apache.solr.api.Command;
@@ -31,6 +30,7 @@ import org.apache.solr.cloud.ConfigSetCmds;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.annotation.JsonProperty;
 import org.apache.solr.common.cloud.ClusterProperties;
+import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ConfigSetParams;
@@ -73,6 +73,42 @@ public class ClusterAPI {
   }
 
   @EndPoint(method = GET,
+          path = "/cluster/node-roles",
+          permission = COLL_READ_PERM)
+  public void roles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+    Map <String, Map<String,Object>> result = new LinkedHashMap<>();
+    collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager().
+            forEachChild(ZkStateReader.NODE_ROLES, (node, data) -> {
+      if(data != null && data.getData() != null) {
+        result.put(node, (Map<String, Object>) Utils.fromJSON(data.getData()));
+      }
+    });
+    rsp.add("node-roles", result);
+  }
+
+  @EndPoint(method = GET,
+          path = "/cluster/node-roles/{role}",
+          permission = COLL_READ_PERM)
+  public void rolesOfNode(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+    String role = req.getPathTemplateValues().get("role");
+
+    List<String> result = new ArrayList<>();
+    collectionsHandler.getCoreContainer().getZkController().getSolrCloudManager().getDistribStateManager().
+            forEachChild(ZkStateReader.NODE_ROLES, (node, data) -> {
+
+      if(data != null && data.getData() != null) {
+        @SuppressWarnings("unchecked")
+        Map<String, Object> roleData = (Map<String, Object>) Utils.fromJSON(data.getData());
+        if(role.equals(roleData.get("role"))) {
+          result.add(node);
+        }
+      }
+    });
+    rsp.add("nodes", result);
+  }
+
+
+  @EndPoint(method = GET,
       path = "/cluster/aliases",
       permission = COLL_READ_PERM)
   public void aliases(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/DistribStateManager.java b/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/DistribStateManager.java
index feae27f..ebd4db7 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/DistribStateManager.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/DistribStateManager.java
@@ -24,8 +24,10 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.NoSuchElementException;
 
+import java.util.function.BiConsumer;
 import org.apache.solr.common.SolrCloseable;
 import org.apache.solr.common.cloud.PerReplicaStates;
+import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.Op;
@@ -141,4 +143,25 @@ public interface DistribStateManager extends SolrCloseable {
       }
     }
   }
+  default void  forEachChild(String path,  BiConsumer<String, VersionedData> fun)
+          throws InterruptedException, IOException, KeeperException {
+    List<String> children = null;
+    try {
+      children = listData(ZkStateReader.NODE_ROLES);
+    } catch (NoSuchElementException e) {
+      return;
+    }
+    if(!children.isEmpty()) {
+      for (String node : children) {
+        VersionedData data = null;
+        try {
+          data = getData(ZkStateReader.NODE_ROLES + "/" + node);
+        } catch (NoSuchElementException e) {
+          //this node probably was deleted
+          continue;
+        }
+        fun.accept(node, data);
+      }
+    }
+  }
 }

[solr] 20/28: removed unnecessary changes

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 37b216c5a3df9d881617c6bda5b18ebe63ce1032
Author: Noble Paul <no...@gmail.com>
AuthorDate: Fri Dec 17 16:43:39 2021 +1100

    removed unnecessary changes
---
 solr/core/src/java/org/apache/solr/cloud/ZkController.java | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index a249e4f..ee0c87f 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -73,7 +73,13 @@ import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.URLUtil;
 import org.apache.solr.common.util.Utils;
-import org.apache.solr.core.*;
+import org.apache.solr.core.CloseHook;
+import org.apache.solr.core.CloudConfig;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.CoreDescriptor;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.core.NodeRoles;
+import org.apache.solr.core.SolrCoreInitializationException;
 import org.apache.solr.handler.component.HttpShardHandler;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.search.SolrIndexSearcher;

[solr] 17/28: removed nocommit

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch jira/SOLR15694
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 4fb3713a7f611a5d92408017530606d080fe44a1
Author: Noble Paul <no...@gmail.com>
AuthorDate: Fri Dec 17 16:36:14 2021 +1100

    removed nocommit
---
 solr/core/src/java/org/apache/solr/handler/ClusterAPI.java | 1 -
 1 file changed, 1 deletion(-)

diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index 446cd9b..9f93c39 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -84,7 +84,6 @@ public class ClusterAPI {
   @EndPoint(method = GET,
           path = "/cluster/node-roles",
           permission = COLL_READ_PERM)
-  // nocommit: it must also output all data nodes that didn't start with -Dsolr.node.roles
   public void roles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     Map<String, Object> result = new LinkedHashMap<>();