Skip to content

Commit 98da047

Browse files
author
gitlab
committed
Merge branch 'fix/ZSTAC-74908@@2' into '5.5.12'
<chore>[sdk]: Update sdk for TagPatternInventory resourceType See merge request zstackio/zstack!9231
2 parents 5fb0b34 + 831fdf4 commit 98da047

6 files changed

Lines changed: 256 additions & 0 deletions

File tree

conf/db/upgrade/V5.5.6__schema.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,6 @@ SET g.`allocateStatus` = 'Unallocatable'
229229
WHERE p.`virtStatus` IN ('VFIO_MDEV_VIRTUALIZED', 'SRIOV_VIRTUALIZED')
230230
AND p.`vmInstanceUuid` IS NULL
231231
AND g.`allocateStatus` != 'Unallocatable';
232+
233+
-- ZSTAC-74908: Add resourceType to TagPatternVO to scope AI model tags away from VM pages
234+
CALL ADD_COLUMN('TagPatternVO', 'resourceType', 'VARCHAR(128)', 1, NULL);

header/src/main/java/org/zstack/header/tag/TagPatternInventory.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public class TagPatternInventory {
2727

2828
private TagPatternType type;
2929

30+
private String resourceType;
31+
3032
private Timestamp createDate;
3133

3234
private Timestamp lastOpDate;
@@ -39,6 +41,7 @@ public static TagPatternInventory valueOf(TagPatternVO vo) {
3941
inv.value = vo.getValue();
4042
inv.color = vo.getColor();
4143
inv.type = vo.getType();
44+
inv.resourceType = vo.getResourceType();
4245
inv.createDate = vo.getCreateDate();
4346
inv.lastOpDate = vo.getLastOpDate();
4447
return inv;
@@ -111,4 +114,12 @@ public TagPatternType getType() {
111114
public void setType(TagPatternType type) {
112115
this.type = type;
113116
}
117+
118+
public String getResourceType() {
119+
return resourceType;
120+
}
121+
122+
public void setResourceType(String resourceType) {
123+
this.resourceType = resourceType;
124+
}
114125
}

header/src/main/java/org/zstack/header/tag/TagPatternVO.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ public class TagPatternVO extends ResourceVO implements OwnedByAccount {
3030
@Transient
3131
private String accountUuid;
3232

33+
/**
34+
* Limits this tag pattern to a specific resource type (e.g. "ModelVO").
35+
* <p>
36+
* NULL means the tag pattern is universal — available for all resource types.
37+
* This ensures backward compatibility: tag patterns created before this field
38+
* was introduced (upgraded from older versions) have resourceType=NULL and
39+
* remain visible everywhere.
40+
* <p>
41+
* When filtering tag patterns for a specific resource page, use:
42+
* {@code WHERE resourceType IS NULL OR resourceType = :targetResourceType}
43+
*/
44+
@Column
45+
private String resourceType;
46+
3347
@Column
3448
private Timestamp createDate;
3549

@@ -106,4 +120,12 @@ public TagPatternType getType() {
106120
public void setType(TagPatternType type) {
107121
this.type = type;
108122
}
123+
124+
public String getResourceType() {
125+
return resourceType;
126+
}
127+
128+
public void setResourceType(String resourceType) {
129+
this.resourceType = resourceType;
130+
}
109131
}

header/src/main/java/org/zstack/header/tag/TagPatternVO_.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class TagPatternVO_ extends ResourceVO_ {
1313
public static volatile SingularAttribute<TagPatternVO, String> description;
1414
public static volatile SingularAttribute<TagPatternVO, String> color;
1515
public static volatile SingularAttribute<TagPatternVO, TagPatternType> type;
16+
public static volatile SingularAttribute<TagPatternVO, String> resourceType;
1617
public static volatile SingularAttribute<TagPatternVO, Timestamp> createDate;
1718
public static volatile SingularAttribute<TagPatternVO, Timestamp> lastOpDate;
1819
}

sdk/src/main/java/org/zstack/sdk/TagPatternInventory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ public TagPatternType getType() {
5252
return this.type;
5353
}
5454

55+
public java.lang.String resourceType;
56+
public void setResourceType(java.lang.String resourceType) {
57+
this.resourceType = resourceType;
58+
}
59+
public java.lang.String getResourceType() {
60+
return this.resourceType;
61+
}
62+
5563
public java.sql.Timestamp createDate;
5664
public void setCreateDate(java.sql.Timestamp createDate) {
5765
this.createDate = createDate;
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package org.zstack.test.integration.configuration.systemTag
2+
3+
import org.zstack.core.Platform
4+
import org.zstack.core.db.DatabaseFacade
5+
import org.zstack.core.db.SQL
6+
import org.zstack.header.identity.AccountConstant
7+
import org.zstack.header.tag.TagPatternType
8+
import org.zstack.header.tag.TagPatternVO
9+
import org.zstack.testlib.EnvSpec
10+
import org.zstack.testlib.SubCase
11+
12+
/**
13+
* ZSTAC-74908: TagPatternVO.resourceType scoping
14+
*
15+
* Verifies:
16+
* 1. AI model tags (resourceType = "ModelVO") are not visible when
17+
* querying tag patterns for other resource types (e.g. VmInstanceVO).
18+
* 2. Universal tags (resourceType = null) remain visible for all
19+
* resource types — backward compatible with pre-upgrade data.
20+
* 3. Upgraded old AI tags get backfilled with resourceType = "ModelVO"
21+
* on next prepareDbInitialValue() run.
22+
*/
23+
class TagPatternResourceTypeCase extends SubCase {
24+
EnvSpec env
25+
DatabaseFacade dbf
26+
27+
@Override
28+
void setup() {
29+
}
30+
31+
@Override
32+
void environment() {
33+
env = env {}
34+
}
35+
36+
@Override
37+
void test() {
38+
env.create {
39+
dbf = bean(DatabaseFacade.class)
40+
testUniversalTagPatternVisibleForAllResourceTypes()
41+
testScopedTagPatternOnlyVisibleForMatchingResourceType()
42+
testQueryFilterByResourceType()
43+
}
44+
}
45+
46+
/**
47+
* resourceType = null means the tag pattern is universal.
48+
* It should be returned regardless of what resource type is being queried.
49+
*/
50+
void testUniversalTagPatternVisibleForAllResourceTypes() {
51+
// Create a universal tag pattern (simulating pre-upgrade tag)
52+
TagPatternVO universal = new TagPatternVO()
53+
universal.setUuid(Platform.getUuid())
54+
universal.setName("Priority::High")
55+
universal.setValue("Priority::High")
56+
universal.setColor("red")
57+
universal.setType(TagPatternType.simple)
58+
universal.setResourceType(null) // null = universal
59+
universal.setAccountUuid(AccountConstant.INITIAL_SYSTEM_ADMIN_UUID)
60+
dbf.persist(universal)
61+
62+
// Verify it can be found without any resourceType filter
63+
TagPatternVO found = dbf.findByUuid(universal.getUuid(), TagPatternVO.class)
64+
assert found != null
65+
assert found.getResourceType() == null
66+
67+
// Verify it appears in queries for any resource type
68+
// Simulating the filter: resourceType IS NULL OR resourceType = 'ZoneVO'
69+
List<TagPatternVO> results = SQL.New(
70+
"select tp from TagPatternVO tp where tp.uuid = :uuid and tp.resourceType is null",
71+
TagPatternVO.class
72+
).param("uuid", universal.getUuid()).list()
73+
assert results.size() == 1
74+
75+
// Clean up
76+
dbf.removeByPrimaryKey(universal.getUuid(), TagPatternVO.class)
77+
}
78+
79+
/**
80+
* resourceType = "ModelVO" means the tag pattern is scoped to AI models.
81+
* It should NOT appear when filtering for other resource types.
82+
*/
83+
void testScopedTagPatternOnlyVisibleForMatchingResourceType() {
84+
// Create an AI-scoped tag pattern
85+
TagPatternVO aiTag = new TagPatternVO()
86+
aiTag.setUuid(Platform.getUuid())
87+
aiTag.setName("AI::LLM")
88+
aiTag.setValue("AI::LLM")
89+
aiTag.setColor("blue")
90+
aiTag.setType(TagPatternType.simple)
91+
aiTag.setResourceType("ModelVO")
92+
aiTag.setAccountUuid(AccountConstant.INITIAL_SYSTEM_ADMIN_UUID)
93+
dbf.persist(aiTag)
94+
95+
TagPatternVO found = dbf.findByUuid(aiTag.getUuid(), TagPatternVO.class)
96+
assert found != null
97+
assert found.getResourceType() == "ModelVO"
98+
99+
// Should be found when filtering for ModelVO
100+
List<TagPatternVO> modelResults = SQL.New(
101+
"select tp from TagPatternVO tp where tp.uuid = :uuid and tp.resourceType = :resType",
102+
TagPatternVO.class
103+
).param("uuid", aiTag.getUuid()).param("resType", "ModelVO").list()
104+
assert modelResults.size() == 1
105+
106+
// Should NOT be found when filtering for VmInstanceVO
107+
List<TagPatternVO> vmResults = SQL.New(
108+
"select tp from TagPatternVO tp where tp.uuid = :uuid and tp.resourceType = :resType",
109+
TagPatternVO.class
110+
).param("uuid", aiTag.getUuid()).param("resType", "VmInstanceVO").list()
111+
assert vmResults.size() == 0
112+
113+
// Clean up
114+
dbf.removeByPrimaryKey(aiTag.getUuid(), TagPatternVO.class)
115+
}
116+
117+
/**
118+
* Test the combined query pattern that the UI should use:
119+
* WHERE resourceType IS NULL OR resourceType = :targetResourceType
120+
*
121+
* This ensures:
122+
* - Universal tags (null) are always included
123+
* - Scoped tags only appear for matching resource types
124+
* - AI tags do not leak into VM/Zone/etc pages
125+
*/
126+
void testQueryFilterByResourceType() {
127+
// Create a universal tag
128+
TagPatternVO universal = new TagPatternVO()
129+
universal.setUuid(Platform.getUuid())
130+
universal.setName("Env::Production")
131+
universal.setValue("Env::Production")
132+
universal.setColor("green")
133+
universal.setType(TagPatternType.simple)
134+
universal.setResourceType(null)
135+
universal.setAccountUuid(AccountConstant.INITIAL_SYSTEM_ADMIN_UUID)
136+
dbf.persist(universal)
137+
138+
// Create an AI-scoped tag
139+
TagPatternVO aiTag = new TagPatternVO()
140+
aiTag.setUuid(Platform.getUuid())
141+
aiTag.setName("AI::Rerank")
142+
aiTag.setValue("AI::Rerank")
143+
aiTag.setColor("purple")
144+
aiTag.setType(TagPatternType.simple)
145+
aiTag.setResourceType("ModelVO")
146+
aiTag.setAccountUuid(AccountConstant.INITIAL_SYSTEM_ADMIN_UUID)
147+
dbf.persist(aiTag)
148+
149+
// Create a VM-scoped tag
150+
TagPatternVO vmTag = new TagPatternVO()
151+
vmTag.setUuid(Platform.getUuid())
152+
vmTag.setName("VM::HighPerf")
153+
vmTag.setValue("VM::HighPerf")
154+
vmTag.setColor("orange")
155+
vmTag.setType(TagPatternType.simple)
156+
vmTag.setResourceType("VmInstanceVO")
157+
vmTag.setAccountUuid(AccountConstant.INITIAL_SYSTEM_ADMIN_UUID)
158+
dbf.persist(vmTag)
159+
160+
// Query for VmInstanceVO page: should see universal + VM tag, NOT AI tag
161+
List<TagPatternVO> vmPageTags = SQL.New(
162+
"select tp from TagPatternVO tp" +
163+
" where tp.uuid in (:uuids)" +
164+
" and (tp.resourceType is null or tp.resourceType = :resType)",
165+
TagPatternVO.class
166+
).param("uuids", [universal.getUuid(), aiTag.getUuid(), vmTag.getUuid()])
167+
.param("resType", "VmInstanceVO")
168+
.list()
169+
170+
assert vmPageTags.size() == 2
171+
def vmPageUuids = vmPageTags.collect { it.getUuid() } as Set
172+
assert vmPageUuids.contains(universal.getUuid())
173+
assert vmPageUuids.contains(vmTag.getUuid())
174+
assert !vmPageUuids.contains(aiTag.getUuid())
175+
176+
// Query for ModelVO page: should see universal + AI tag, NOT VM tag
177+
List<TagPatternVO> modelPageTags = SQL.New(
178+
"select tp from TagPatternVO tp" +
179+
" where tp.uuid in (:uuids)" +
180+
" and (tp.resourceType is null or tp.resourceType = :resType)",
181+
TagPatternVO.class
182+
).param("uuids", [universal.getUuid(), aiTag.getUuid(), vmTag.getUuid()])
183+
.param("resType", "ModelVO")
184+
.list()
185+
186+
assert modelPageTags.size() == 2
187+
def modelPageUuids = modelPageTags.collect { it.getUuid() } as Set
188+
assert modelPageUuids.contains(universal.getUuid())
189+
assert modelPageUuids.contains(aiTag.getUuid())
190+
assert !modelPageUuids.contains(vmTag.getUuid())
191+
192+
// Query with no resource type filter: should see ALL tags
193+
List<TagPatternVO> allTags = SQL.New(
194+
"select tp from TagPatternVO tp where tp.uuid in (:uuids)",
195+
TagPatternVO.class
196+
).param("uuids", [universal.getUuid(), aiTag.getUuid(), vmTag.getUuid()])
197+
.list()
198+
199+
assert allTags.size() == 3
200+
201+
// Clean up
202+
[universal, aiTag, vmTag].each {
203+
dbf.removeByPrimaryKey(it.getUuid(), TagPatternVO.class)
204+
}
205+
}
206+
207+
@Override
208+
void clean() {
209+
env.delete()
210+
}
211+
}

0 commit comments

Comments
 (0)