4949import org .elasticsearch .client .RestClientBuilder ;
5050import org .elasticsearch .client .WarningsHandler ;
5151import org .elasticsearch .cluster .metadata .Metadata ;
52+ import org .elasticsearch .cluster .metadata .ProjectId ;
5253import org .elasticsearch .common .Strings ;
5354import org .elasticsearch .common .bytes .BytesArray ;
5455import org .elasticsearch .common .bytes .BytesReference ;
5556import org .elasticsearch .common .settings .SecureString ;
5657import org .elasticsearch .common .settings .Settings ;
5758import org .elasticsearch .common .ssl .PemUtils ;
59+ import org .elasticsearch .common .util .CollectionUtils ;
5860import org .elasticsearch .common .util .concurrent .ThreadContext ;
5961import org .elasticsearch .common .util .set .Sets ;
6062import org .elasticsearch .common .xcontent .XContentHelper ;
9294import org .junit .After ;
9395import org .junit .AfterClass ;
9496import org .junit .Before ;
97+ import org .junit .BeforeClass ;
9598
9699import java .io .BufferedReader ;
97100import java .io .IOException ;
118121import java .util .HashMap ;
119122import java .util .HashSet ;
120123import java .util .List ;
124+ import java .util .Locale ;
121125import java .util .Map ;
122126import java .util .Objects ;
123127import java .util .Optional ;
@@ -266,6 +270,9 @@ public static boolean hasXPack() {
266270 private static RestClient cleanupClient ;
267271
268272 private static boolean multiProjectEnabled ;
273+ private static String activeProject ;
274+ private static Set <String > extraProjects ;
275+ private static boolean projectsConfigured = false ;
269276
270277 public enum ProductFeature {
271278 XPACK ,
@@ -357,6 +364,15 @@ protected static boolean testFeatureServiceInitialized() {
357364 return testFeatureService != ALL_FEATURES ;
358365 }
359366
367+ @ BeforeClass
368+ public static void initializeProjectIds () {
369+ // The active project-id is slightly longer, and has a fixed prefix so that it's easier to pick in error messages etc.
370+ activeProject = "active00" + randomAlphaOfLength (8 ).toLowerCase (Locale .ROOT );
371+ extraProjects = randomSet (1 , 3 , () -> randomAlphaOfLength (12 ).toLowerCase (Locale .ROOT ));
372+ // TODO do this in a different way
373+ multiProjectEnabled = Objects .equals (System .getProperty ("test.multi_project.enabled" ), "true" );
374+ }
375+
360376 @ Before
361377 public void initClient () throws IOException {
362378 if (client == null ) {
@@ -367,17 +383,17 @@ public void initClient() throws IOException {
367383 assert testFeatureServiceInitialized () == false ;
368384 clusterHosts = parseClusterHosts (getTestRestCluster ());
369385 logger .info ("initializing REST clients against {}" , clusterHosts );
370- var clientSettings = restClientSettings ();
386+ var clientSettings = addProjectIdToSettings ( restClientSettings () );
371387 var adminSettings = restAdminSettings ();
388+ var cleanupSettings = cleanupClientSettings ();
372389 var hosts = clusterHosts .toArray (new HttpHost [0 ]);
373390 client = buildClient (clientSettings , hosts );
374391 adminClient = clientSettings .equals (adminSettings ) ? client : buildClient (adminSettings , hosts );
375- cleanupClient = getCleanupClient ( );
392+ cleanupClient = adminSettings . equals ( cleanupSettings ) ? adminClient : buildClient ( cleanupSettings , hosts );
376393
377394 availableFeatures = EnumSet .of (ProductFeature .LEGACY_TEMPLATES );
378395 Set <String > versions = new HashSet <>();
379396 boolean serverless = false ;
380- String multiProjectPluginVariant = null ;
381397
382398 for (Map <?, ?> nodeInfo : getNodesInfo (adminClient ).values ()) {
383399 var nodeVersion = nodeInfo .get ("version" ).toString ();
@@ -407,11 +423,6 @@ public void initClient() throws IOException {
407423 if (moduleName .startsWith ("serverless-" )) {
408424 serverless = true ;
409425 }
410- if (moduleName .contains ("test-multi-project" )) {
411- multiProjectPluginVariant = "test" ;
412- } else if (moduleName .contains ("serverless-multi-project" )) {
413- multiProjectPluginVariant = "serverless" ;
414- }
415426 }
416427 if (serverless ) {
417428 availableFeatures .removeAll (
@@ -432,22 +443,11 @@ public void initClient() throws IOException {
432443 .flatMap (Optional ::stream )
433444 .collect (Collectors .toSet ());
434445 assert semanticNodeVersions .isEmpty () == false || serverless ;
435-
436- if (multiProjectPluginVariant != null ) {
437- final Request settingRequest = new Request (
438- "GET" ,
439- "/_cluster/settings?include_defaults&filter_path=*." + multiProjectPluginVariant + ".multi_project.enabled"
440- );
441- settingRequest .setOptions (RequestOptions .DEFAULT .toBuilder ().setWarningsHandler (WarningsHandler .PERMISSIVE ));
442- final var response = entityAsMap (adminClient .performRequest (settingRequest ));
443- multiProjectEnabled = Boolean .parseBoolean (
444- ObjectPath .evaluate (response , "defaults." + multiProjectPluginVariant + ".multi_project.enabled" )
445- );
446- }
447-
448446 testFeatureService = createTestFeatureService (getClusterStateFeatures (adminClient ), semanticNodeVersions );
449447 }
450448
449+ configureProjects ();
450+
451451 assert testFeatureServiceInitialized ();
452452 assert client != null ;
453453 assert adminClient != null ;
@@ -456,6 +456,40 @@ public void initClient() throws IOException {
456456 assert nodesVersions != null ;
457457 }
458458
459+ private void configureProjects () throws IOException {
460+ if (projectsConfigured || multiProjectEnabled == false ) {
461+ return ;
462+ }
463+ projectsConfigured = true ;
464+ createProject (activeProject );
465+ for (var project : extraProjects ) {
466+ createProject (project );
467+ }
468+
469+ // The admin client does not set a project id, and can see all projects
470+ assertProjectIds (
471+ adminClient (),
472+ CollectionUtils .concatLists (List .of (Metadata .DEFAULT_PROJECT_ID .id (), activeProject ), extraProjects )
473+ );
474+ // The test client can only see the project it targets
475+ assertProjectIds (client (), List .of (activeProject ));
476+ }
477+
478+ @ After
479+ public final void assertEmptyProjects () throws Exception {
480+ if (multiProjectEnabled == false ) {
481+ return ;
482+ }
483+ assertEmptyProject (Metadata .DEFAULT_PROJECT_ID .id ());
484+ for (var project : extraProjects ) {
485+ assertEmptyProject (project );
486+ }
487+ }
488+
489+ public static String activeProject () {
490+ return activeProject ;
491+ }
492+
459493 protected final TestFeatureService createTestFeatureService (
460494 Map <String , Set <String >> clusterStateFeatures ,
461495 Set <Version > semanticNodeVersions
@@ -1604,10 +1638,6 @@ protected Settings restClientSettings() {
16041638 String token = basicAuthHeaderValue (username , new SecureString (password .toCharArray ()));
16051639 builder .put (ThreadContext .PREFIX + ".Authorization" , token );
16061640 }
1607- if (System .getProperty ("tests.rest.project.id" ) != null ) {
1608- final var projectId = System .getProperty ("tests.rest.project.id" );
1609- builder .put (ThreadContext .PREFIX + ".X-Elastic-Project-Id" , projectId );
1610- }
16111641 return builder .build ();
16121642 }
16131643
@@ -1621,9 +1651,21 @@ protected Settings restAdminSettings() {
16211651 /**
16221652 * Returns the REST client used for cleaning up the cluster.
16231653 */
1624- protected RestClient getCleanupClient () {
1625- assert adminClient != null ;
1626- return adminClient ;
1654+ protected Settings cleanupClientSettings () {
1655+ if (multiProjectEnabled == false ) {
1656+ return restAdminSettings ();
1657+ }
1658+ return addProjectIdToSettings (restAdminSettings ());
1659+ }
1660+
1661+ private Settings addProjectIdToSettings (Settings settings ) {
1662+ if (multiProjectEnabled == false ) {
1663+ return settings ;
1664+ }
1665+ return Settings .builder ()
1666+ .put (settings )
1667+ .put (ThreadContext .PREFIX + "." + Task .X_ELASTIC_PROJECT_ID_HTTP_HEADER , activeProject )
1668+ .build ();
16271669 }
16281670
16291671 /**
@@ -2716,10 +2758,9 @@ protected static void assertResultMap(
27162758
27172759 protected void createProject (String project ) throws IOException {
27182760 assert multiProjectEnabled ;
2719- RestClient client = adminClient ();
27202761 final Request request = new Request ("PUT" , "/_project/" + project );
27212762 try {
2722- final Response response = client .performRequest (request );
2763+ final Response response = adminClient () .performRequest (request );
27232764 logger .info ("Created project {} : {}" , project , response .getStatusLine ());
27242765 } catch (ResponseException e ) {
27252766 logger .error ("Failed to create project: {}" , project );
@@ -2750,6 +2791,21 @@ private Collection<String> getProjectIds(RestClient client) throws IOException {
27502791 }
27512792 }
27522793
2794+ protected void cleanUpProjects () throws IOException {
2795+ final var projectIds = getProjectIds (adminClient ());
2796+ for (String projectId : projectIds ) {
2797+ if (projectId .equals (ProjectId .DEFAULT .id ())) {
2798+ continue ;
2799+ }
2800+ deleteProject (projectId );
2801+ }
2802+ }
2803+
2804+ private void deleteProject (String project ) throws IOException {
2805+ final Request request = new Request ("DELETE" , "/_project/" + project );
2806+ cleanupClient ().performRequest (request );
2807+ }
2808+
27532809 protected void assertEmptyProject (String projectId ) throws IOException {
27542810 assert multiProjectEnabled ;
27552811 final Request request = new Request ("GET" , "_cluster/state/metadata,routing_table,customs" );
@@ -2790,16 +2846,12 @@ protected void assertEmptyProject(String projectId) throws IOException {
27902846 if (indexTemplates != null ) {
27912847 var templateNames = indexTemplates .keySet ().stream ().filter (name -> isXPackTemplate (name ) == false ).toList ();
27922848 assertThat ("Project [" + projectId + "] should not have index templates" , templateNames , empty ());
2793- } else if (projectId .equals (Metadata .DEFAULT_PROJECT_ID .id ())) {
2794- fail ("Expected default project to have standard templates, but was null" );
27952849 }
27962850
27972851 final Map <String , Object > componentTemplates = state .evaluate ("metadata.component_template.component_template" );
27982852 if (componentTemplates != null ) {
27992853 var templateNames = componentTemplates .keySet ().stream ().filter (name -> isXPackTemplate (name ) == false ).toList ();
28002854 assertThat ("Project [" + projectId + "] should not have component templates" , templateNames , empty ());
2801- } else if (projectId .equals (Metadata .DEFAULT_PROJECT_ID .id ())) {
2802- fail ("Expected default project to have standard component templates, but was null" );
28032855 }
28042856
28052857 final List <Map <String , ?>> pipelines = state .evaluate ("metadata.ingest.pipeline" );
@@ -2809,8 +2861,6 @@ protected void assertEmptyProject(String projectId) throws IOException {
28092861 .filter (id -> isXPackIngestPipeline (id ) == false )
28102862 .toList ();
28112863 assertThat ("Project [" + projectId + "] should not have ingest pipelines" , pipelineNames , empty ());
2812- } else if (projectId .equals (Metadata .DEFAULT_PROJECT_ID .id ())) {
2813- fail ("Expected default project to have standard ingest pipelines, but was null" );
28142864 }
28152865
28162866 if (has (ProductFeature .ILM )) {
@@ -2819,8 +2869,6 @@ protected void assertEmptyProject(String projectId) throws IOException {
28192869 var policyNames = new HashSet <>(ilmPolicies .keySet ());
28202870 policyNames .removeAll (preserveILMPolicyIds ());
28212871 assertThat ("Project [" + projectId + "] should not have ILM Policies" , policyNames , empty ());
2822- } else if (projectId .equals (Metadata .DEFAULT_PROJECT_ID .id ())) {
2823- fail ("Expected default project to have standard ILM policies, but was null" );
28242872 }
28252873 }
28262874 }
0 commit comments