Skip to content

Commit 024d063

Browse files
csvirishawkins
andauthored
Resource version comparison utility (#2988)
Signed-off-by: Attila Mészáros <a_meszaros@apple.com> Co-authored-by: Steven Hawkins <shawkins@redhat.com>
1 parent 48513b9 commit 024d063

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.reconciler;
17+
18+
import io.javaoperatorsdk.operator.OperatorException;
19+
20+
public class NonComparableResourceVersionException extends OperatorException {
21+
22+
public NonComparableResourceVersionException(String message) {
23+
super(message);
24+
}
25+
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtils.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,4 +450,58 @@ public static <P extends HasMetadata> P addFinalizerWithSSA(
450450
e);
451451
}
452452
}
453+
454+
public static int compareResourceVersions(String v1, String v2) {
455+
var v1Length = v1.length();
456+
if (v1Length == 0) {
457+
throw new NonComparableResourceVersionException("Resource version (1) is empty");
458+
}
459+
var v2Length = v2.length();
460+
if (v2Length == 0) {
461+
throw new NonComparableResourceVersionException("Resource version (2) is empty");
462+
}
463+
var maxLength = Math.max(v1Length, v2Length);
464+
boolean v1LeadingZero = true;
465+
boolean v2LeadingZero = true;
466+
int comparison = 0;
467+
for (int i = 0; i < maxLength; i++) {
468+
char char1 = 0;
469+
if (i < v1Length) {
470+
char1 = v1.charAt(i);
471+
if (v1LeadingZero) {
472+
if (char1 == '0') {
473+
throw new NonComparableResourceVersionException(
474+
"Resource version (1) cannot begin with 0");
475+
}
476+
v1LeadingZero = false;
477+
}
478+
if (!Character.isDigit(char1)) {
479+
throw new NonComparableResourceVersionException(
480+
"Non numeric characters in resource version (1): " + char1);
481+
}
482+
}
483+
if (i < v2Length) {
484+
var char2 = v2.charAt(i);
485+
if (v2LeadingZero) {
486+
if (char2 == '0') {
487+
throw new NonComparableResourceVersionException(
488+
"Resource version (2) cannot begin with 0");
489+
}
490+
v2LeadingZero = false;
491+
}
492+
if (!Character.isDigit(char2)) {
493+
throw new NonComparableResourceVersionException(
494+
"Non numeric characters in resource version (2): " + char2);
495+
}
496+
if (char1 == 0) {
497+
comparison = -1;
498+
} else if (comparison == 0) {
499+
comparison = Character.compare(char1, char2);
500+
}
501+
} else {
502+
comparison = 1;
503+
}
504+
}
505+
return comparison;
506+
}
453507
}

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/PrimaryUpdateAndCacheUtilsTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import org.junit.jupiter.api.BeforeEach;
2222
import org.junit.jupiter.api.Test;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
2325

2426
import io.fabric8.kubernetes.api.model.HasMetadata;
2527
import io.fabric8.kubernetes.client.KubernetesClient;
@@ -37,6 +39,7 @@
3739
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
3840

3941
import static io.javaoperatorsdk.operator.api.reconciler.PrimaryUpdateAndCacheUtils.DEFAULT_MAX_RETRY;
42+
import static io.javaoperatorsdk.operator.api.reconciler.PrimaryUpdateAndCacheUtils.compareResourceVersions;
4043
import static org.assertj.core.api.Assertions.assertThat;
4144
import static org.junit.jupiter.api.Assertions.assertThrows;
4245
import static org.mockito.ArgumentMatchers.any;
@@ -47,6 +50,8 @@
4750

4851
class PrimaryUpdateAndCacheUtilsTest {
4952

53+
private static final Logger log = LoggerFactory.getLogger(PrimaryUpdateAndCacheUtilsTest.class);
54+
5055
Context<TestCustomResource> context = mock(Context.class);
5156
KubernetesClient client = mock(KubernetesClient.class);
5257
Resource resource = mock(Resource.class);
@@ -176,4 +181,52 @@ void cachePollTimeouts() {
176181
10L));
177182
assertThat(ex.getMessage()).contains("Timeout");
178183
}
184+
185+
@Test
186+
public void compareResourceVersionsTest() {
187+
assertThat(compareResourceVersions("11", "22")).isNegative();
188+
assertThat(compareResourceVersions("22", "11")).isPositive();
189+
assertThat(compareResourceVersions("1", "1")).isZero();
190+
assertThat(compareResourceVersions("11", "11")).isZero();
191+
assertThat(compareResourceVersions("123", "2")).isPositive();
192+
assertThat(compareResourceVersions("3", "211")).isNegative();
193+
194+
assertThrows(
195+
NonComparableResourceVersionException.class, () -> compareResourceVersions("aa", "22"));
196+
assertThrows(
197+
NonComparableResourceVersionException.class, () -> compareResourceVersions("11", "ba"));
198+
assertThrows(
199+
NonComparableResourceVersionException.class, () -> compareResourceVersions("", "22"));
200+
assertThrows(
201+
NonComparableResourceVersionException.class, () -> compareResourceVersions("11", ""));
202+
assertThrows(
203+
NonComparableResourceVersionException.class, () -> compareResourceVersions("01", "123"));
204+
assertThrows(
205+
NonComparableResourceVersionException.class, () -> compareResourceVersions("123", "01"));
206+
assertThrows(
207+
NonComparableResourceVersionException.class, () -> compareResourceVersions("3213", "123a"));
208+
assertThrows(
209+
NonComparableResourceVersionException.class, () -> compareResourceVersions("321", "123a"));
210+
}
211+
212+
// naive performance test that compares the work case scenario for the parsing and non-parsing
213+
// variants
214+
@Test
215+
public void compareResourcePerformanceTest() {
216+
var execNum = 30000000;
217+
var startTime = System.currentTimeMillis();
218+
for (int i = 0; i < execNum; i++) {
219+
var res = compareResourceVersions("123456788", "123456789");
220+
}
221+
var dur1 = System.currentTimeMillis() - startTime;
222+
log.info("Duration without parsing: {}", dur1);
223+
startTime = System.currentTimeMillis();
224+
for (int i = 0; i < execNum; i++) {
225+
var res = Long.parseLong("123456788") > Long.parseLong("123456789");
226+
}
227+
var dur2 = System.currentTimeMillis() - startTime;
228+
log.info("Duration with parsing: {}", dur2);
229+
230+
assertThat(dur1).isLessThan(dur2);
231+
}
179232
}

0 commit comments

Comments
 (0)