Skip to content

Commit

Permalink
[Java]: add a means for a custom appVersion validation scheme to be u…
Browse files Browse the repository at this point in the history
…sed for appVersion compatibility checking.
  • Loading branch information
tmontgomery committed Jul 18, 2022
1 parent ab1a248 commit 299a300
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2014-2022 Real Logic Limited.
*
* Licensed 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
*
* https://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 io.aeron.cluster;

import org.agrona.SemanticVersion;

/**
* Class to be used for determining AppVersion compatibility.
*
* Default is to use {@link org.agrona.SemanticVersion} semantics for check.
*/
public class AppVersionValidator
{
/**
* Check version compatibility between configured context appVersion and appVersion in
* new leadership term or snapshot.
*
* @param contextAppVersion configured appVersion value from context.
* @param appVersionUnderTest to check against configured appVersion.
* @return true for compatible or false for not compatible.
*/
public boolean isVersionCompatible(final int contextAppVersion, final int appVersionUnderTest)
{
return SemanticVersion.major(contextAppVersion) == SemanticVersion.major(appVersionUnderTest);
}
}
32 changes: 32 additions & 0 deletions aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,7 @@ public static final class Context implements Cloneable
private LogPublisher logPublisher;
private EgressPublisher egressPublisher;
private DutyCycleTracker dutyCycleTracker;
private AppVersionValidator appVersionValidator;
private boolean isLogMdc;

/**
Expand Down Expand Up @@ -1354,6 +1355,11 @@ public void conclude()
epochClock = SystemEpochClock.INSTANCE;
}

if (null == appVersionValidator)
{
appVersionValidator = new AppVersionValidator();
}

if (null == clusterTimeConsumerSupplier)
{
clusterTimeConsumerSupplier = (ctx) -> (timestamp) -> {};
Expand Down Expand Up @@ -1696,6 +1702,32 @@ public int appVersion()
return appVersion;
}

/**
* User assigned application version validator implementation used to check version compatibility.
*
* The default validator uses {@link org.agrona.SemanticVersion} semantics.
*
* @param appVersionValidator for user application.
* @return this for fluent API.
*/
public Context appVersionValidator(final AppVersionValidator appVersionValidator)
{
this.appVersionValidator = appVersionValidator;
return this;
}

/**
* User assigned application version validator implementation used to check version compatibility.
*
* The default validator uses {@link org.agrona.SemanticVersion} semantics.
*
* @return AppVersionValidator in use.
*/
public AppVersionValidator appVersionValidator()
{
return appVersionValidator;
}

/**
* Get level at which files should be sync'ed to disk.
* <ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ void onNewLeadershipTerm(
appVersion,
isStartup);

if (SemanticVersion.major(ctx.appVersion()) != SemanticVersion.major(appVersion))
if (!ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), appVersion))
{
ctx.errorHandler().onError(new ClusterException(
"incompatible version: " + SemanticVersion.toString(ctx.appVersion()) +
Expand Down Expand Up @@ -1324,7 +1324,7 @@ void onReplayNewLeadershipTermEvent(
unexpectedTermination();
}

if (SemanticVersion.major(ctx.appVersion()) != SemanticVersion.major(appVersion))
if (!ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), appVersion))
{
ctx.countedErrorHandler().onError(new ClusterException(
"incompatible version: " + SemanticVersion.toString(ctx.appVersion()) +
Expand Down Expand Up @@ -2708,7 +2708,7 @@ private void loadSnapshot(final RecordingLog.Snapshot snapshot, final AeronArchi
}

final int appVersion = snapshotLoader.appVersion();
if (SemanticVersion.major(ctx.appVersion()) != SemanticVersion.major(appVersion))
if (!ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), appVersion))
{
throw new ClusterException(
"incompatible version: " + SemanticVersion.toString(ctx.appVersion()) +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ void onNewLeadershipTermEvent(
final TimeUnit timeUnit,
final int appVersion)
{
if (SemanticVersion.major(ctx.appVersion()) != SemanticVersion.major(appVersion))
if (!ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), appVersion))
{
ctx.errorHandler().onError(new ClusterException(
"incompatible version: " + SemanticVersion.toString(ctx.appVersion()) +
Expand Down Expand Up @@ -807,7 +807,7 @@ private void loadState(final Image image, final AeronArchive archive)
}

final int appVersion = snapshotLoader.appVersion();
if (SemanticVersion.major(ctx.appVersion()) != SemanticVersion.major(appVersion))
if (!ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), appVersion))
{
throw new ClusterException(
"incompatible app version: " + SemanticVersion.toString(ctx.appVersion()) +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import io.aeron.*;
import io.aeron.archive.client.AeronArchive;
import io.aeron.cluster.AppVersionValidator;
import io.aeron.cluster.client.ClusterException;
import io.aeron.cluster.codecs.mark.ClusterComponentType;
import io.aeron.cluster.codecs.mark.MarkFileHeaderEncoder;
Expand Down Expand Up @@ -616,6 +617,7 @@ public static final class Context implements Cloneable
private String aeronDirectoryName = CommonContext.getAeronDirectoryName();
private Aeron aeron;
private DutyCycleTracker dutyCycleTracker;
private AppVersionValidator appVersionValidator;
private boolean ownsAeronClient;

private ClusteredService clusteredService;
Expand Down Expand Up @@ -666,6 +668,11 @@ public void conclude()
idleStrategySupplier = Configuration.idleStrategySupplier(null);
}

if (null == appVersionValidator)
{
appVersionValidator = new AppVersionValidator();
}

if (null == epochClock)
{
epochClock = SystemEpochClock.INSTANCE;
Expand Down Expand Up @@ -832,6 +839,32 @@ public int appVersion()
return appVersion;
}

/**
* User assigned application version validator implementation used to check version compatibility.
*
* The default validator uses {@link org.agrona.SemanticVersion} semantics.
*
* @param appVersionValidator for user application.
* @return this for fluent API.
*/
public Context appVersionValidator(final AppVersionValidator appVersionValidator)
{
this.appVersionValidator = appVersionValidator;
return this;
}

/**
* User assigned application version validator implementation used to check version compatibility.
*
* The default validator uses {@link org.agrona.SemanticVersion} semantics.
*
* @return AppVersionValidator in use.
*/
public AppVersionValidator appVersionValidator()
{
return appVersionValidator;
}

/**
* Set the id for this cluster instance. This must match with the Consensus Module.
*
Expand Down

0 comments on commit 299a300

Please sign in to comment.