Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/draft-release-notes-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ jobs:
with:
config-name: draft-release-notes-config.yml
tag: (None)
version: 3.5.0.0
version: 3.6.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/test_security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ jobs:
# switching the user, as OpenSearch cluster can only be started as root/Administrator on linux-deb/linux-rpm/windows-zip.
run: |
chown -R 1000:1000 `pwd`
su `id -un 1000` -c "whoami && java -version && ./gradlew integTest -Dhttps=true -Dtests.opensearch.username=admin -Dtests.opensearch.password=admin -Dusername=admin -Dpassword=admin -Dtests.opensearch.secure=true -Dsecurity=true ${{ matrix.resource_sharing_flag }}"
su `id -un 1000` -c "whoami && java -version && ./gradlew integTest -Dhttps=true -Dtests.opensearch.username=admin -Dtests.opensearch.password=admin -Dusername=admin -Dpassword=admin -Dtests.opensearch.secure=true -Dsecurity=true ${{ matrix.resource_sharing_flag }}"
37 changes: 28 additions & 9 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ buildscript {
opensearch_group = "org.opensearch"

isSnapshot = "true" == System.getProperty("build.snapshot", "true")
opensearch_version = System.getProperty("opensearch.version", "3.5.0-SNAPSHOT")
opensearch_version = System.getProperty("opensearch.version", "3.6.0-SNAPSHOT")
buildVersionQualifier = System.getProperty("build.version_qualifier", "")
// 2.0.0-rc1-SNAPSHOT -> 2.0.0.0-rc1-SNAPSHOT
version_tokens = opensearch_version.tokenize('-')
Expand Down Expand Up @@ -596,14 +596,25 @@ testClusters.integTest {
}
}

// For job-scheduler and reports-scheduler, the latest opensearch releases appear to be 1.1.0.0.
String baseVersion = "2.20.0"
String baseVersion = "2.19.5"
String bwcVersion = baseVersion + ".0"
String baseName = "reportsSchedulerBwcCluster"
String bwcFilePath = "src/test/resources/bwc"
String bwcJobSchedulerURL = "https://aws.oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&g=org.opensearch.plugin&a=opensearch-job-scheduler&v=$bwcVersion-SNAPSHOT&p=zip"
String bwcReportsSchedulerURL = "https://aws.oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&g=org.opensearch.plugin&a=opensearch-reports-scheduler&v=$bwcVersion-SNAPSHOT&p=zip"
String bwcSnapshotVersion = baseVersion + "-SNAPSHOT"
String groupPath = "org/opensearch/plugin"
String base = "https://ci.opensearch.org/ci/dbc/snapshots/maven"

def resolveZipSnapshotValue = { String metadataXmlUrl ->
def xml = new URL(metadataXmlUrl).text
def m = (xml =~ /<snapshotVersion>[\s\S]*?<extension>zip<\/extension>[\s\S]*?<value>([^<]+)<\/value>[\s\S]*?<\/snapshotVersion>/)
if (!m.find()) {
throw new GradleException("Could not find zip snapshot <value> in ${metadataXmlUrl}")
}
return m.group(1)
}

String jobSchedulerArtifact = "opensearch-job-scheduler"
String reportsSchedulerArtifact = "opensearch-reports-scheduler"

2.times {i ->
testClusters {
Expand All @@ -617,13 +628,17 @@ String bwcSnapshotVersion = baseVersion + "-SNAPSHOT"
return new RegularFile() {
@Override
File getAsFile() {
String metadataUrl = "${base}/${groupPath}/${jobSchedulerArtifact}/${bwcVersion}-SNAPSHOT/maven-metadata.xml"
String snapshotValue = resolveZipSnapshotValue(metadataUrl)
String fileName = "${jobSchedulerArtifact}-${snapshotValue}.zip"
String downloadUrl = "${base}/${groupPath}/${jobSchedulerArtifact}/${bwcVersion}-SNAPSHOT/${fileName}"
File dir = new File(bwcFilePath + "/job-scheduler/" + bwcVersion)
if (!dir.exists()) {
dir.mkdirs()
}
File file = new File(dir, "opensearch-job-scheduler-" + bwcVersion + ".zip")
File file = new File(dir, fileName)
if (!file.exists()) {
new URL(bwcJobSchedulerURL).withInputStream{ ins -> file.withOutputStream{ it << ins }}
new URL(downloadUrl).withInputStream{ ins -> file.withOutputStream{ it << ins }}
}
return fileTree(bwcFilePath + "/job-scheduler/" + bwcVersion).getSingleFile()
}
Expand All @@ -636,13 +651,17 @@ String bwcSnapshotVersion = baseVersion + "-SNAPSHOT"
return new RegularFile() {
@Override
File getAsFile() {
String metadataUrl = "${base}/${groupPath}/${reportsSchedulerArtifact}/${bwcVersion}-SNAPSHOT/maven-metadata.xml"
String snapshotValue = resolveZipSnapshotValue(metadataUrl)
String fileName = "${reportsSchedulerArtifact}-${snapshotValue}.zip"
String downloadUrl = "${base}/${groupPath}/${reportsSchedulerArtifact}/${bwcVersion}-SNAPSHOT/${fileName}"
File dir = new File(bwcFilePath + "/reports-scheduler/" + bwcVersion)
if (!dir.exists()) {
dir.mkdirs()
}
File file = new File(dir, "opensearch-reports-scheduler-" + bwcVersion + ".zip")
File file = new File(dir, fileName)
if (!file.exists()) {
new URL(bwcReportsSchedulerURL).withInputStream{ ins -> file.withOutputStream{ it << ins }}
new URL(downloadUrl).withInputStream{ ins -> file.withOutputStream{ it << ins }}
}
return fileTree(bwcFilePath + "/reports-scheduler/" + bwcVersion).getSingleFile()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class ReportsSchedulerExtension : ResourceSharingExtension {
object : ResourceProvider {
override fun resourceType(): String = Utils.REPORT_INSTANCE_TYPE
override fun resourceIndexName(): String = ReportInstancesIndex.REPORT_INSTANCES_INDEX_NAME
override fun parentType(): String = Utils.REPORT_DEFINITION_TYPE
override fun parentIdField(): String = "reportDefinitionDetails.id"
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.opensearch.reportsscheduler.model.RestTag.TENANT_FIELD
import org.opensearch.reportsscheduler.model.RestTag.UPDATED_TIME_FIELD
import org.opensearch.reportsscheduler.resources.Utils
import org.opensearch.reportsscheduler.resources.Utils.shouldUseResourceAuthz
import org.apache.lucene.search.TotalHits
import org.opensearch.reportsscheduler.settings.PluginSettings
import org.opensearch.reportsscheduler.util.PluginClient
import org.opensearch.reportsscheduler.util.SecureIndexClient
Expand Down Expand Up @@ -160,31 +161,75 @@ internal object ReportInstancesIndex {
pluginClient: PluginClient?
): ReportInstanceSearchResults {
createIndex()
val tenantQuery = QueryBuilders.termsQuery(TENANT_FIELD, tenant)

if (pluginClient != null && shouldUseResourceAuthz(Utils.REPORT_INSTANCE_TYPE)) {
// Resource-sharing path: resolve accessible instance IDs in the plugin layer.
//
// Step 1 — directly visible instances (DLS-filtered via pluginClient)
val directInstanceIds = searchInstanceIds(
QueryBuilders.boolQuery().filter(tenantQuery),
pluginClient
)

// Step 2 — accessible definition IDs (DLS-filtered via pluginClient)
val definitionIds = searchDefinitionIds(tenant, pluginClient)

// Step 3 — instances whose parent definition is accessible (admin client, no DLS)
val inheritedInstanceIds: Set<String> = if (definitionIds.isEmpty()) {
emptySet()
} else {
searchInstanceIdsByDefinitionIds(tenantQuery, definitionIds)
}

val allIds = directInstanceIds + inheritedInstanceIds
log.info(
"$LOG_PREFIX:getAllReportInstances directIds=${directInstanceIds.size}" +
" definitionIds=${definitionIds.size} inheritedIds=${inheritedInstanceIds.size}" +
" total=${allIds.size}"
)

if (allIds.isEmpty()) {
return ReportInstanceSearchResults(from.toLong(), 0L, TotalHits.Relation.EQUAL_TO, emptyList())
}

// Step 4 — fetch full docs for the union, with pagination applied
val query = QueryBuilders.boolQuery()
.filter(tenantQuery)
.filter(QueryBuilders.idsQuery().addIds(*allIds.toTypedArray()))
val sourceBuilder = SearchSourceBuilder()
.timeout(TimeValue(PluginSettings.operationTimeoutMs, TimeUnit.MILLISECONDS))
.sort(UPDATED_TIME_FIELD)
.size(maxItems)
.from(from)
.query(query)
val searchRequest = SearchRequest().indices(REPORT_INSTANCES_INDEX_NAME).source(sourceBuilder)
val response = client.search(searchRequest).actionGet(PluginSettings.operationTimeoutMs)
val result = ReportInstanceSearchResults(from.toLong(), response)
log.info(
"$LOG_PREFIX:getAllReportInstances from:$from maxItems:$maxItems" +
" retCount:${result.objectList.size} totalCount:${result.totalHits}"
)
return result
}

// Legacy path (resource-sharing disabled)
val sourceBuilder = SearchSourceBuilder()
.timeout(TimeValue(PluginSettings.operationTimeoutMs, TimeUnit.MILLISECONDS))
.sort(UPDATED_TIME_FIELD)
.size(maxItems)
.from(from)
val tenantQuery = QueryBuilders.termsQuery(TENANT_FIELD, tenant)
if (access.isNotEmpty()) {
val accessQuery = QueryBuilders.termsQuery(ACCESS_LIST_FIELD, access)
val query = QueryBuilders.boolQuery()
query.filter(tenantQuery)
query.filter(accessQuery)
sourceBuilder.query(query)
sourceBuilder.query(
QueryBuilders.boolQuery()
.filter(tenantQuery)
.filter(QueryBuilders.termsQuery(ACCESS_LIST_FIELD, access))
)
} else {
sourceBuilder.query(tenantQuery)
}
val searchRequest = SearchRequest()
.indices(REPORT_INSTANCES_INDEX_NAME)
.source(sourceBuilder)
val actionFuture =
if (pluginClient != null && shouldUseResourceAuthz(Utils.REPORT_INSTANCE_TYPE)) {
pluginClient.search(searchRequest)
} else {
client.search(searchRequest)
}
val response = actionFuture.actionGet(PluginSettings.operationTimeoutMs)
val searchRequest = SearchRequest().indices(REPORT_INSTANCES_INDEX_NAME).source(sourceBuilder)
val response = client.search(searchRequest).actionGet(PluginSettings.operationTimeoutMs)
val result = ReportInstanceSearchResults(from.toLong(), response)
log.info(
"$LOG_PREFIX:getAllReportInstances from:$from, maxItems:$maxItems," +
Expand All @@ -193,6 +238,58 @@ internal object ReportInstancesIndex {
return result
}

/**
* Returns the set of instance document IDs matching [query] via [pluginClient] (DLS-filtered).
* Fetches only `_id` — no source needed.
*/
private fun searchInstanceIds(query: org.opensearch.index.query.QueryBuilder, pluginClient: PluginClient): Set<String> {
val sourceBuilder = SearchSourceBuilder()
.timeout(TimeValue(PluginSettings.operationTimeoutMs, TimeUnit.MILLISECONDS))
.size(10_000)
.fetchSource(false)
.query(query)
val request = SearchRequest().indices(REPORT_INSTANCES_INDEX_NAME).source(sourceBuilder)
val response = pluginClient.search(request).actionGet(PluginSettings.operationTimeoutMs)
return response.hits.hits.map { it.id }.toSet()
}

/**
* Returns the set of definition document IDs accessible to the current user via [pluginClient] (DLS-filtered).
*/
private fun searchDefinitionIds(tenant: String, pluginClient: PluginClient): Set<String> {
val query = QueryBuilders.termsQuery(TENANT_FIELD, tenant)
val sourceBuilder = SearchSourceBuilder()
.timeout(TimeValue(PluginSettings.operationTimeoutMs, TimeUnit.MILLISECONDS))
.size(10_000)
.fetchSource(false)
.query(query)
val request = SearchRequest()
.indices(ReportDefinitionsIndex.REPORT_DEFINITIONS_INDEX_NAME)
.source(sourceBuilder)
val response = pluginClient.search(request).actionGet(PluginSettings.operationTimeoutMs)
return response.hits.hits.map { it.id }.toSet()
}

/**
* Returns instance IDs (via admin client, no DLS) whose `reportDefinitionDetails.id` is in [definitionIds].
*/
private fun searchInstanceIdsByDefinitionIds(
tenantQuery: org.opensearch.index.query.QueryBuilder,
definitionIds: Set<String>
): Set<String> {
val query = QueryBuilders.boolQuery()
.filter(tenantQuery)
.filter(QueryBuilders.termsQuery("reportDefinitionDetails.id", definitionIds.toList()))
val sourceBuilder = SearchSourceBuilder()
.timeout(TimeValue(PluginSettings.operationTimeoutMs, TimeUnit.MILLISECONDS))
.size(10_000)
.fetchSource(false)
.query(query)
val request = SearchRequest().indices(REPORT_INSTANCES_INDEX_NAME).source(sourceBuilder)
val response = client.search(request).actionGet(PluginSettings.operationTimeoutMs)
return response.hits.hits.map { it.id }.toSet()
}

/**
* update Report instance details for given id
* @param reportInstance the Report instance details data
Expand Down
7 changes: 5 additions & 2 deletions src/main/resources/report-instances-mapping.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ properties:
type: keyword
status:
type: keyword
reportDefinitionDetails.id:
type: keyword
reportDefinitionDetails:
type: object
properties:
id:
type: keyword
all_shared_principals:
type: keyword
2 changes: 1 addition & 1 deletion src/main/resources/resource-action-groups.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ resource_types:
allowed_actions:
- 'cluster:admin/opendistro/reports/instance/*'
- 'cluster:admin/opendistro/reports/menu/download'
- 'cluster:admin/security/resource/share'
- 'cluster:admin/security/resource/share'
Loading
Loading