Skip to content

IGNITE-29026 Introduce extension points in compatibility testcontainers - #13540

Open
wernerdv wants to merge 1 commit into
apache:masterfrom
wernerdv:IGNITE-29026
Open

IGNITE-29026 Introduce extension points in compatibility testcontainers#13540
wernerdv wants to merge 1 commit into
apache:masterfrom
wernerdv:IGNITE-29026

Conversation

@wernerdv

Copy link
Copy Markdown
Contributor

Thank you for submitting the pull request to the Apache Ignite.

In order to streamline the review of the contribution
we ask you to ensure the following steps have been taken:

The Contribution Checklist

  • There is a single JIRA ticket related to the pull request.
  • The web-link to the pull request is attached to the JIRA ticket.
  • The JIRA ticket has the Patch Available state.
  • The pull request body describes changes that have been made.
    The description explains WHAT and WHY was made instead of HOW.
  • The pull request title is treated as the final commit message.
    The following pattern must be used: IGNITE-XXXX Change summary where XXXX - number of JIRA issue.
  • A reviewer has been mentioned through the JIRA comments
    (see the Maintainers list)
  • The pull request has been checked by the Teamcity Bot and
    the green visa attached to the JIRA ticket (see tab PR Check at TC.Bot - Instance 1 or TC.Bot - Instance 2)

Notes

If you need any help, please email dev@ignite.apache.org or ask anу advice on http://asf.slack.com #ignite channel.

@wernerdv
wernerdv force-pushed the IGNITE-29026 branch 5 times, most recently from f593694 to c715e55 Compare September 1, 2026 12:17
@wernerdv wernerdv changed the title IGNITE-29026 Add the ability to customize IgniteContainer configs IGNITE-29026 Add extension hooks to compatibility test framework Sep 1, 2026
@wernerdv wernerdv changed the title IGNITE-29026 Add extension hooks to compatibility test framework IGNITE-29026 Introduce extension points in compatibility testcontainers Sep 1, 2026

List<String> res = new ArrayList<>();

res.add(clsPath);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

classResources() returns the top-level class twice on the file: path, so building the test-classes jar always fails.

Line 486 unconditionally seeds the list with res.add(clsPath), and then the file: branch's filter on line 496 explicitly matches name.equals(simple + ".class") and re-adds the very same resource as pkg + f.getName(). testClassesJar() calls out.putNextEntry(new JarEntry(resName)) per element, and ZipOutputStream.putNextEntry throws ZipException: duplicate entry on the second occurrence.

This is the default execution path: all four classes in testClasses() live in modules/compatibility/src/test/java, so under surefire they resolve from target/test-classes (protocol file). Since testClassesJar() is called from the constructor (line 200), IgniteRebalanceOnUpgradeTest fails at new IgniteContainer(...) before any container starts.

this.hostname = hostname;
this.consistentId = consistentId;
workDirPath = WORK_DIR_PATH + "/" + hostname;
rootDir = rootDirPath();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor calls the new overridable hooks before a subclass can initialize its own state: rootDirPath() (line 149), commonConfigResource()/sourceConfigResource() (lines 198-200), testClasses() via testClassesJar() (line 200) and waitStrategy() (line 215).

Since extensibility is the point of the PR, could these be resolved lazily on first use (or moved into configure() / containerIsCreating(), which Testcontainers already provides for exactly this)? At minimum the javadoc should state that overrides must not depend on subclass instance state.


/** Jar holding {@link #TEST_CLASSES}, injected so the old image can load it. */
/** Jar holding the {@link #testClasses() test classes}, injected so the old image can load it. */
private static volatile File testClassesJar;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testClasses() is now overridable per subclass, but the resulting jar is still memoized in private static volatile File testClassesJar (line 114) with no key. targetLibsArchive (line 117) / libsArchive() (line 534) have the same shape, and the latter embeds that jar.

Within one surefire fork, whichever container is constructed first wins: if the base test builds the jar with the four default classes and a subclass overriding testClasses() runs afterwards, the subclass silently reuses the stale jar and its node dies with ClassNotFoundException — with the outcome depending on test execution order.

Suggest keying the cache on the effective class list (e.g. ConcurrentHashMap<List, File>) or making it instance-level.

}

/** Builds the node containers. */
protected void initContainers() throws Exception {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving container creation from the constructor into initContainers() makes start() non-idempotent: the method always appends to the pre-existing containers list, so a second start() — or a retry after Startables.deepStart(...).join() fails — yields 2N containers with duplicate hostnames, duplicate consistent IDs and duplicate fixed host ports, after which activateCluster(containers.size()) waits for a baseline size that can never be reached.

Also, containers() used to be populated right after construction and now returns an empty list until start() runs. Nothing in the current test depends on that, but it is a silent contract change for subclasses that might want to inspect containers before starting.

A if (!containers.isEmpty()) return; guard (or a started flag) would cover both.

* @param idx Node index.
* @return The node container.
*/
protected IgniteContainer container(String imageName, Network net, List<String> consistentIds, int idx) throws Exception {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

container(String imageName, Network net, List consistentIds, int idx) re-passes imageName, net and consistentIds, all of which are already fields of the instance, and hands the override the whole list plus an index rather than the id it needs. protected IgniteContainer container(int idx) would be a cleaner hook; if the parameters are kept for readability, passing consistentIds.get(idx) instead of the list would still be an improvement.

* @param nestedPrefix Package-based prefix of the nested classes, e.g. {@code org/apache/foo/Simple$}.
* @return Resource names of the class and its nested classes.
*/
private static Collection<String> classResources(String clsPath, String nestedPrefix) throws IOException {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two small redundancies in classResources():

  • nestedPrefix is a parameter but only the jar: branch uses it — the file: branch recomputes pkg/simple from clsPath itself. It could just be derived inside the method and the parameter dropped.
  • The method resolves getResource(clsPath) and the caller then resolves getResource(resName) again for the same top-level class.

Comment on lines +34 to +52
private final Network net;

/** Image name. */
private final String imageName;

/** Consistent ID's. */
private final List<String> consistentIds;

/**
* @param imageName Image name.
* @param consistentIds Consistent ID's.
*/
public IgniteClusterContainer(String imageName, List<String> consistentIds) throws Exception {
public IgniteClusterContainer(String imageName, List<String> consistentIds) {
this.imageName = imageName;
this.consistentIds = consistentIds;

net = Network.newNetwork();
containers = new ArrayList<>(consistentIds.size());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep net initialization at field declaration? It does not depend on constructor arguments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants