Skip to content

Commit 5ed9a17

Browse files
Update groovy tests to work with ImageV2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ac171d9 commit 5ed9a17

File tree

9 files changed

+116
-25
lines changed

9 files changed

+116
-25
lines changed

qa-tests-backend/src/main/groovy/util/Helpers.groovy

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package util
22

3+
import java.nio.ByteBuffer
34
import java.nio.file.Path
45
import java.nio.file.Paths
6+
import java.security.MessageDigest
57
import java.text.SimpleDateFormat
68

79
import groovy.transform.CompileDynamic
@@ -194,6 +196,42 @@ class Helpers {
194196
return false
195197
}
196198

199+
// Mirrors Go's pkg/uuid.NewV5FromNonUUIDs: SHA-256 the namespace string to derive
200+
// a UUID namespace, then produce a standard UUIDv5 (SHA-1) from that namespace and name.
201+
static String newV5FromNonUUIDs(String ns, String name) {
202+
byte[] sha256 = MessageDigest.getInstance("SHA-256").digest(ns.getBytes("UTF-8"))
203+
long msb = ByteBuffer.wrap(sha256, 0, 8).getLong()
204+
long lsb = ByteBuffer.wrap(sha256, 8, 8).getLong()
205+
UUID nsUUID = new UUID(msb, lsb)
206+
207+
MessageDigest sha1 = MessageDigest.getInstance("SHA-1")
208+
sha1.update(uuidToBytes(nsUUID))
209+
sha1.update(name.getBytes("UTF-8"))
210+
byte[] hash = sha1.digest()
211+
212+
hash[6] = (byte) ((hash[6] & 0x0F) | 0x50) // version 5
213+
hash[8] = (byte) ((hash[8] & 0x3F) | 0x80) // variant RFC 4122
214+
long msbResult = ByteBuffer.wrap(hash, 0, 8).getLong()
215+
long lsbResult = ByteBuffer.wrap(hash, 8, 8).getLong()
216+
return new UUID(msbResult, lsbResult).toString()
217+
}
218+
219+
private static byte[] uuidToBytes(UUID uuid) {
220+
ByteBuffer bb = ByteBuffer.allocate(16)
221+
bb.putLong(uuid.getMostSignificantBits())
222+
bb.putLong(uuid.getLeastSignificantBits())
223+
return bb.array()
224+
}
225+
226+
// Mirrors Go's pkg/images/utils.NewImageV2ID: generates a deterministic UUIDv5
227+
// from the image full name and digest.
228+
static String generateImageV2ID(String fullName, String digest) {
229+
if (!fullName || !digest) {
230+
return ""
231+
}
232+
return newV5FromNonUUIDs(fullName, digest)
233+
}
234+
197235
private static final Set<String> VOLATILE_ANNOTATIONS_TO_IGNORE = [
198236
"machineconfiguration.openshift.io/lastSyncedControllerConfigResourceVersion"
199237
].toSet()

qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ class AdmissionControllerTest extends BaseSpecification {
8383
// Pre-scan the image so Central has cached scan results for CVE-based policy evaluation.
8484
ImageService.scanImage(SCAN_INLINE_IMAGE_NAME_WITH_SHA)
8585

86-
ImageOuterClass.Image image = ImageService.getImage(SCAN_INLINE_IMAGE_SHA, false)
86+
def imageId = flattenImageDataEnabled ? TEST_IMAGE_V2_ID : SCAN_INLINE_IMAGE_SHA
87+
ImageOuterClass.Image image = ImageService.getImage(imageId, false)
8788
assert image
8889
assert !image.getNotesList().contains(ImageOuterClass.Image.Note.MISSING_METADATA)
8990

qa-tests-backend/src/test/groovy/BaseSpecification.groovy

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ class BaseSpecification extends Specification {
4848
static final String TEST_IMAGE = "quay.io/rhacs-eng/qa-multi-arch:nginx-2.0.3@$TEST_IMAGE_SHA"
4949
static final String TEST_IMAGE_NAME_WITH_SHA = TEST_IMAGE
5050
static final String TEST_IMAGE_SHA = "sha256:ebecc1ad41054eaef19ef9c84e0d95551dfbdebbf0875fd407aee697e4be3860"
51+
// UUIDv5 of TEST_IMAGE (full name) and TEST_IMAGE_SHA (digest), used as image ID when FlattenImageData is enabled.
52+
static final String TEST_IMAGE_V2_ID = Helpers.generateImageV2ID(TEST_IMAGE, TEST_IMAGE_SHA)
5153

5254
static final String RUN_ID
5355

@@ -75,6 +77,7 @@ class BaseSpecification extends Specification {
7577
public static String coreImageIntegrationId = null
7678

7779
public static boolean scannerV4Enabled = false
80+
public static boolean flattenImageDataEnabled = false
7881

7982
private static synchronizedGlobalSetup() {
8083
synchronized(BaseSpecification) {
@@ -134,6 +137,9 @@ class BaseSpecification extends Specification {
134137
scannerV4Enabled = FeatureFlagService.isFeatureFlagEnabled("ROX_SCANNER_V4")
135138
LOG.info "Scanner V4 enabled: ${scannerV4Enabled}"
136139

140+
flattenImageDataEnabled = Env.get("ROX_FLATTEN_IMAGE_DATA") == "true"
141+
LOG.info "Flatten Image Data enabled: ${flattenImageDataEnabled}"
142+
137143
if (ClusterService.isOpenShift4()) {
138144
assert Env.mustGetOrchestratorType() == OrchestratorTypes.OPENSHIFT,
139145
"Set CLUSTER=OPENSHIFT when testing OpenShift"

qa-tests-backend/src/test/groovy/CSVTest.groovy

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,19 @@ class CSVTest extends BaseSpecification {
134134
return "CVE Type:IMAGE_CVE+"
135135
}
136136

137+
def getTestImageId() {
138+
return flattenImageDataEnabled ? TEST_IMAGE_V2_ID : TEST_IMAGE_SHA
139+
}
140+
141+
def getDeploymentUid() {
142+
return CVE_DEPLOYMENT.deploymentUid
143+
}
144+
145+
def getFixableCvesInImageQuery() {
146+
def imageFilter = flattenImageDataEnabled ? "Image ID" : "Image Sha"
147+
return "${imageFilter}:${getTestImageId()}+Fixable:true"
148+
}
149+
137150
Map<String, Object> payload(String id) {
138151
def pagination = new Pagination(0, 0, new SortOption("cvss", true))
139152
return [
@@ -227,10 +240,9 @@ class CSVTest extends BaseSpecification {
227240
where:
228241
"Data is"
229242

230-
description | id | query
231-
"FIXABLE_CVES_IN_IMAGE_QUERY" | TEST_IMAGE_SHA | "Image Sha:${TEST_IMAGE_SHA}+Fixable:true"
232-
"FIXABLE_CVES_IN_DEPLOYMENT_QUERY" | CVE_DEPLOYMENT.deploymentUid |
233-
"Deployment ID:${CVE_DEPLOYMENT.deploymentUid}+Fixable:true"
243+
description | id | query
244+
"FIXABLE_CVES_IN_IMAGE_QUERY" | getTestImageId() | getFixableCvesInImageQuery()
245+
"FIXABLE_CVES_IN_DEPLOYMENT_QUERY" | getDeploymentUid() | "Deployment ID:${getDeploymentUid()}+Fixable:true"
234246
}
235247

236248
@EqualsAndHashCode(includeFields = true)

qa-tests-backend/src/test/groovy/GlobalSearch.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ class GlobalSearch extends BaseSpecification {
3434
SearchServiceOuterClass.SearchCategory.NAMESPACES,
3535
SearchServiceOuterClass.SearchCategory.IMAGES,
3636
SearchServiceOuterClass.SearchCategory.DEPLOYMENTS)
37+
if (flattenImageDataEnabled) {
38+
EXPECTED_DEPLOYMENT_CATEGORIES.add(SearchServiceOuterClass.SearchCategory.IMAGES_V2)
39+
EXPECTED_IMAGE_CATEGORIES.add(SearchServiceOuterClass.SearchCategory.IMAGES_V2)
40+
}
3741

3842
orchestrator.createDeployment(DEPLOYMENT)
3943
assert Services.waitForDeployment(DEPLOYMENT)

qa-tests-backend/src/test/groovy/ImageManagementTest.groovy

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,8 @@ class ImageManagementTest extends BaseSpecification {
218218
then:
219219
"Assert that riskScore is non-zero"
220220
withRetry(10, 3) {
221-
def image = ImageService.getImage(TEST_IMAGE_SHA)
221+
def imageId = flattenImageDataEnabled ? TEST_IMAGE_V2_ID : TEST_IMAGE_SHA
222+
def image = ImageService.getImage(imageId)
222223
assert image != null && image.riskScore != 0
223224
}
224225
}
@@ -241,7 +242,8 @@ class ImageManagementTest extends BaseSpecification {
241242
then:
242243
"Assert that riskScore is non-zero"
243244
withRetry(10, 3) {
244-
def image = ImageService.getImage(TEST_IMAGE_SHA)
245+
def imageId = flattenImageDataEnabled ? TEST_IMAGE_V2_ID : TEST_IMAGE_SHA
246+
def image = ImageService.getImage(imageId)
245247
assert image != null && image.riskScore != 0
246248
}
247249

qa-tests-backend/src/test/groovy/SACTest.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ class SACTest extends BaseSpecification {
404404
tokenName | category | numResults
405405
NOACCESSTOKEN | "Cluster" | 0
406406
"searchDeploymentsToken" | "Deployment" | 1
407-
"searchImagesToken" | "Image" | 1
407+
"searchImagesToken" | "Image" | (flattenImageDataEnabled ? 2 : 1)
408408
}
409409

410410
@Unroll

qa-tests-backend/src/test/groovy/UpgradesTest.groovy

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import static util.Helpers.evaluateWithRetry
2+
13
import com.google.protobuf.util.JsonFormat
24
import groovy.io.FileType
35
import io.grpc.StatusRuntimeException
@@ -84,8 +86,17 @@ class UpgradesTest extends BaseSpecification {
8486
def nodes = NodeService.getNodes()
8587
assert nodes.size() != 0
8688
"Image API returns non-zero values on upgrade"
87-
def images = ImageService.getImages()
88-
assert images.size() != 0
89+
if (flattenImageDataEnabled) {
90+
// When FlattenImageData is enabled, the reprocessor moves images from the
91+
// images table to images_v2 after upgrade, which may take some time.
92+
evaluateWithRetry(30, 10) {
93+
def images = ImageService.getImages()
94+
assert images.size() != 0
95+
}
96+
} else {
97+
def images = ImageService.getImages()
98+
assert images.size() != 0
99+
}
89100
}
90101

91102
@Unroll
@@ -101,8 +112,20 @@ class UpgradesTest extends BaseSpecification {
101112
then:
102113
"Check that we got the correct number of #resourceType from GraphQL "
103114
assert resultRet.getValue() != null
104-
def items = resultRet.getValue()[resourceType]
105-
assert items.size() >= minResults
115+
if (flattenImageDataEnabled) {
116+
// When FlattenImageData is enabled, the reprocessor moves images from the
117+
// images table to images_v2 after upgrade, which may take some time.
118+
evaluateWithRetry(30, 10) {
119+
def retryResultRet = gqlService.Call(getQuery(resourceType), [ query: searchQuery ])
120+
assert retryResultRet.getCode() == 200
121+
assert retryResultRet.getValue() != null
122+
def retryItems = retryResultRet.getValue()[resourceType]
123+
assert retryItems.size() >= minResults
124+
}
125+
} else {
126+
def items = resultRet.getValue()[resourceType]
127+
assert items.size() >= minResults
128+
}
106129

107130
where:
108131
"Data Inputs Are:"

qa-tests-backend/src/test/groovy/VulnMgmtTest.groovy

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import io.stackrox.proto.storage.Cve.VulnerabilitySeverity
33
import org.junit.Assume
44
import services.GraphQLService
55
import services.ImageService
6+
import util.Helpers
67

78
import spock.lang.Tag
89
import spock.lang.Unroll
@@ -29,6 +30,9 @@ class VulnMgmtTest extends BaseSpecification {
2930
static final private String UBUNTU_IMAGE =
3031
"quay.io/rhacs-eng/qa:ubuntu-22.04-amd64"
3132

33+
static final private MODERATE = VulnerabilitySeverity.MODERATE_VULNERABILITY_SEVERITY
34+
static final private LOW = VulnerabilitySeverity.LOW_VULNERABILITY_SEVERITY
35+
3236
private static final EMBEDDED_IMAGE_QUERY = """
3337
query getImage(\$id: ID!, \$query: String) {
3438
result: fullImage(id: \$id) {
@@ -187,14 +191,14 @@ query getComponentId(\$imageId: ID!, \$componentQuery: String) {
187191
Assume.assumeFalse(scannerV4Enabled)
188192

189193
expect:
190-
verifySeveritiesAndCvss(imageDigest, component, cve, severity, cvss)
194+
verifySeveritiesAndCvss(imageDigest, imageName, component, cve, severity, cvss)
191195

192196
where:
193197
"Data inputs are: "
194198

195-
imageDigest | component | cve | severity | cvss
196-
RHEL_IMAGE_LEGACY_DIGEST | "glib2" | "CVE-2019-13012" | VulnerabilitySeverity.LOW_VULNERABILITY_SEVERITY | 4.4
197-
UBUNTU_IMAGE_DIGEST | "gnupg2" | "CVE-2022-3219" | VulnerabilitySeverity.LOW_VULNERABILITY_SEVERITY | 3.3
199+
imageDigest | imageName | component | cve | severity | cvss
200+
RHEL_IMAGE_LEGACY_DIGEST | RHEL_IMAGE_LEGACY | "glib2" | "CVE-2019-13012" | LOW | 4.4
201+
UBUNTU_IMAGE_DIGEST | UBUNTU_IMAGE | "gnupg2" | "CVE-2022-3219" | LOW | 3.3
198202
}
199203

200204
@Unroll
@@ -203,27 +207,28 @@ query getComponentId(\$imageId: ID!, \$componentQuery: String) {
203207
Assume.assumeTrue(scannerV4Enabled)
204208

205209
expect:
206-
verifySeveritiesAndCvss(imageDigest, component, cve, severity, cvss)
210+
verifySeveritiesAndCvss(imageDigest, imageName, component, cve, severity, cvss)
207211

208212
where:
209213
"Data inputs are: "
210214

211-
imageDigest | component | cve | severity | cvss
212-
RHEL_IMAGE_DIGEST | "python3" | "CVE-2025-11468" | VulnerabilitySeverity.MODERATE_VULNERABILITY_SEVERITY | 4.5
213-
UBUNTU_IMAGE_DIGEST | "gpgv" | "CVE-2022-3219" | VulnerabilitySeverity.LOW_VULNERABILITY_SEVERITY | 3.3
215+
imageDigest | imageName | component | cve | severity | cvss
216+
RHEL_IMAGE_DIGEST | RHEL_IMAGE | "python3" | "CVE-2025-11468" | MODERATE | 4.5
217+
UBUNTU_IMAGE_DIGEST | UBUNTU_IMAGE | "gpgv" | "CVE-2022-3219" | LOW | 3.3
214218
}
215219

216-
private void verifySeveritiesAndCvss(String imageDigest, String component, String cve,
220+
private void verifySeveritiesAndCvss(String imageDigest, String imageName, String component, String cve,
217221
VulnerabilitySeverity severity, double cvss) {
218222
def gqlService = new GraphQLService()
219223

220224
def query = "CVE:${cve}"
225+
def imageId = flattenImageDataEnabled ? Helpers.generateImageV2ID(imageName, imageDigest) : imageDigest
221226

222227
// Fetch the component ID dynamically since IDs now include image ID and index
223-
def componentID = getComponentIDForImage(gqlService, imageDigest, component)
228+
def componentID = getComponentIDForImage(gqlService, imageId, component)
224229

225230
def embeddedImageRes = gqlService.Call(getEmbeddedImageQuery(),
226-
[id: imageDigest, query: query])
231+
[id: imageId, query: query])
227232

228233
// Expanded instead of using hasErrors() for easier debugging if there are errors
229234
// as the test framework will actually print out the errors now
@@ -235,12 +240,12 @@ query getComponentId(\$imageId: ID!, \$componentQuery: String) {
235240
def embeddedImageResVuln = embeddedImageRes.value.result.scan.imageComponents[0].imageVulnerabilities[0]
236241

237242
def topLevelImageRes = gqlService.Call(getTopLevelImageQuery(),
238-
[id: imageDigest, query: query])
243+
[id: imageId, query: query])
239244
assert topLevelImageRes.hasNoErrors()
240245
def topLevelImageResVuln = topLevelImageRes.value.result.vulns[0]
241246

242247
def fixableCVEImageRes = gqlService.Call(getImageFixableCVEQuery(),
243-
[id: imageDigest, vulnQuery: query, scopeQuery: "Image SHA:${imageDigest}"])
248+
[id: imageId, vulnQuery: query, scopeQuery: "Image SHA:${imageDigest}"])
244249
assert fixableCVEImageRes.hasNoErrors()
245250
def fixableCVEImageResVuln = fixableCVEImageRes.value.result.vulnerabilities[0]
246251

0 commit comments

Comments
 (0)