Skip to content

Commit 44c191f

Browse files
author
gitlab
committed
Merge branch 'feature/ZSTAC-80472@@2' into '5.5.12'
<feature>[core]: support resnotify webhook infrastructure See merge request zstackio/zstack!9337
2 parents c3ed9d0 + f5459df commit 44c191f

17 files changed

Lines changed: 850 additions & 27 deletions

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,30 @@ WHERE `uuid` IN (
6262
)
6363
AND `metricName` = 'VRouterCPUAverageUsedUtilization';
6464

65+
-- ZSTAC-80472: Resource notification webhook tables
66+
CREATE TABLE IF NOT EXISTS `zstack`.`ResNotifySubscriptionVO` (
67+
`uuid` VARCHAR(32) NOT NULL UNIQUE,
68+
`name` VARCHAR(255) DEFAULT NULL,
69+
`description` VARCHAR(2048) DEFAULT NULL,
70+
`resourceTypes` TEXT DEFAULT NULL,
71+
`eventTypes` VARCHAR(256) DEFAULT NULL,
72+
`type` VARCHAR(32) NOT NULL DEFAULT 'WEBHOOK',
73+
`state` VARCHAR(32) NOT NULL DEFAULT 'Enabled',
74+
`accountUuid` VARCHAR(32) DEFAULT NULL,
75+
`lastOpDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
76+
`createDate` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
77+
PRIMARY KEY (`uuid`),
78+
INDEX `idx_ResNotifySubscriptionVO_accountUuid` (`accountUuid`),
79+
INDEX `idx_ResNotifySubscriptionVO_type_state` (`type`, `state`),
80+
CONSTRAINT `fkResNotifySubscriptionVOResourceVO` FOREIGN KEY (`uuid`) REFERENCES `ResourceVO` (`uuid`) ON DELETE CASCADE
81+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
6582

83+
CREATE TABLE IF NOT EXISTS `zstack`.`ResNotifyWebhookRefVO` (
84+
`uuid` VARCHAR(32) NOT NULL UNIQUE,
85+
`webhookUrl` TEXT NOT NULL,
86+
`secret` VARCHAR(256) DEFAULT NULL,
87+
`customHeaders` TEXT,
88+
PRIMARY KEY (`uuid`),
89+
CONSTRAINT `fk_ResNotifyWebhookRefVO_ResNotifySubscriptionVO`
90+
FOREIGN KEY (`uuid`) REFERENCES `ResNotifySubscriptionVO`(`uuid`) ON DELETE CASCADE
91+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

core/src/main/java/org/zstack/core/db/DatabaseFacade.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,6 @@ public interface DatabaseFacade {
8484
String getDbVersion();
8585

8686
void installEntityLifeCycleCallback(Class entityClass, EntityEvent evt, EntityLifeCycleCallback cb);
87+
88+
void uninstallEntityLifeCycleCallback(Class entityClass, EntityEvent evt, EntityLifeCycleCallback cb);
8789
}

core/src/main/java/org/zstack/core/db/DatabaseFacadeImpl.java

Lines changed: 75 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.zstack.core.db;
22

3+
import com.google.common.collect.Maps;
4+
import java.util.concurrent.CopyOnWriteArrayList;
35
import org.hibernate.exception.ConstraintViolationException;
46
import org.springframework.beans.factory.annotation.Autowired;
57
import org.springframework.core.NestedExceptionUtils;
@@ -66,7 +68,7 @@ class EntityInfo {
6668
Field eoSoftDeleteColumn;
6769
Class eoClass;
6870
Class voClass;
69-
Map<EntityEvent, EntityLifeCycleCallback> listeners = new HashMap<EntityEvent, EntityLifeCycleCallback>();
71+
Map<EntityEvent, List<EntityLifeCycleCallback>> listeners = Maps.newConcurrentMap();
7072

7173
EntityInfo(Class voClazz) {
7274
voClass = voClazz;
@@ -82,12 +84,16 @@ class EntityInfo {
8284
EO at = (EO) voClazz.getAnnotation(EO.class);
8385
if (at != null) {
8486
eoClass = at.EOClazz();
85-
DebugUtils.Assert(eoClass != null, String.format("cannot find EO entity specified by VO entity[%s]", voClazz.getName()));
87+
DebugUtils.Assert(eoClass != null,
88+
String.format("cannot find EO entity specified by VO entity[%s]", voClazz.getName()));
8689
eoPrimaryKeyField = FieldUtils.getAnnotatedField(Id.class, eoClass);
87-
DebugUtils.Assert(eoPrimaryKeyField != null, String.format("cannot find primary key field(@Id annotated) in EO entity[%s]", eoClass.getName()));
90+
DebugUtils.Assert(eoPrimaryKeyField != null, String
91+
.format("cannot find primary key field(@Id annotated) in EO entity[%s]", eoClass.getName()));
8892
eoPrimaryKeyField.setAccessible(true);
8993
eoSoftDeleteColumn = FieldUtils.getField(at.softDeletedColumn(), eoClass);
90-
DebugUtils.Assert(eoSoftDeleteColumn != null, String.format("cannot find soft delete column[%s] in EO entity[%s]", at.softDeletedColumn(), eoClass.getName()));
94+
DebugUtils.Assert(eoSoftDeleteColumn != null,
95+
String.format("cannot find soft delete column[%s] in EO entity[%s]", at.softDeletedColumn(),
96+
eoClass.getName()));
9197
eoSoftDeleteColumn.setAccessible(true);
9298
}
9399

@@ -108,7 +114,8 @@ private void buildSoftDeletionCascade() {
108114
for (final SoftDeletionCascade at : ats.value()) {
109115
final Class parent = at.parent();
110116
if (!parent.isAnnotationPresent(Entity.class)) {
111-
throw new CloudRuntimeException(String.format("class[%s] has annotation @SoftDeletionCascade but its parent class[%s] is not annotated by @Entity",
117+
throw new CloudRuntimeException(String.format(
118+
"class[%s] has annotation @SoftDeletionCascade but its parent class[%s] is not annotated by @Entity",
112119
voClass, parent));
113120
}
114121

@@ -131,7 +138,8 @@ public List<Class> getEntityClassForSoftDeleteEntityExtension() {
131138
@Override
132139
@Transactional
133140
public void postSoftDelete(Collection entityIds, Class entityClass) {
134-
String sql = String.format("delete from %s me where me.%s in (:ids)", voClass.getSimpleName(), at.joinColumn());
141+
String sql = String.format("delete from %s me where me.%s in (:ids)", voClass.getSimpleName(),
142+
at.joinColumn());
135143
Query q = getEntityManager().createQuery(sql);
136144
q.setParameter("ids", entityIds);
137145
q.executeUpdate();
@@ -148,7 +156,8 @@ private void buildInheritanceDeletionExtension() {
148156

149157
final Class parent = voClass.getSuperclass();
150158
if (!parent.isAnnotationPresent(Entity.class)) {
151-
throw new CloudRuntimeException(String.format("class[%s] has annotation @PrimaryKeyJoinColumn but its parent class[%s] is not annotated by @Entity",
159+
throw new CloudRuntimeException(String.format(
160+
"class[%s] has annotation @PrimaryKeyJoinColumn but its parent class[%s] is not annotated by @Entity",
152161
voClass, parent));
153162
}
154163

@@ -245,17 +254,21 @@ private void updateEO(Object entity, RuntimeException de) {
245254
}
246255

247256
SQLIntegrityConstraintViolationException me = (SQLIntegrityConstraintViolationException) rootCause;
248-
if (!(me.getErrorCode() == 1062 && "23000".equals(me.getSQLState()) && me.getMessage().contains("PRIMARY"))) {
257+
if (!(me.getErrorCode() == 1062 && "23000".equals(me.getSQLState())
258+
&& me.getMessage().contains("PRIMARY"))) {
249259
throw de;
250260
}
251261

252262
if (!hasEO()) {
253263
throw de;
254264
}
255265

256-
// at this point, the error is caused by a update tried on VO entity which has been soft deleted. This is mostly
257-
// caused by a deletion cascade(e.g deleting host will cause vm running on it to be deleted, and deleting vm is trying to return capacity
258-
// to host which has been soft deleted, because vm deletion is executed in async manner). In this case, we make the update to EO table
266+
// at this point, the error is caused by a update tried on VO entity which has
267+
// been soft deleted. This is mostly
268+
// caused by a deletion cascade(e.g deleting host will cause vm running on it to
269+
// be deleted, and deleting vm is trying to return capacity
270+
// to host which has been soft deleted, because vm deletion is executed in async
271+
// manner). In this case, we make the update to EO table
259272

260273
Object idval = getEOPrimaryKeyValue(entity);
261274
Object eo = getEntityManager().find(eoClass, idval);
@@ -360,8 +373,10 @@ private void hardDelete(Collection ids) {
360373

361374
@Transactional
362375
private void nativeSqlDelete(Collection ids) {
363-
// native sql can avoid JPA cascades a deletion to parent entity when deleting a child entity
364-
String sql = String.format("delete from %s where %s in (:ids)", voClass.getSimpleName(), voPrimaryKeyField.getName());
376+
// native sql can avoid JPA cascades a deletion to parent entity when deleting a
377+
// child entity
378+
String sql = String.format("delete from %s where %s in (:ids)", voClass.getSimpleName(),
379+
voPrimaryKeyField.getName());
365380
Query q = getEntityManager().createNativeQuery(sql);
366381
q.setParameter("ids", ids);
367382
q.executeUpdate();
@@ -418,7 +433,8 @@ List listByPrimaryKeys(Collection ids, int offset, int length) {
418433
sql = String.format("select e from %s e", voClass.getSimpleName());
419434
query = getEntityManager().createQuery(sql, voClass);
420435
} else {
421-
sql = String.format("select e from %s e where e.%s in (:ids)", voClass.getSimpleName(), voPrimaryKeyField.getName());
436+
sql = String.format("select e from %s e where e.%s in (:ids)", voClass.getSimpleName(),
437+
voPrimaryKeyField.getName());
422438
query = getEntityManager().createQuery(sql, voClass);
423439
query.setParameter("ids", ids);
424440
}
@@ -429,7 +445,8 @@ List listByPrimaryKeys(Collection ids, int offset, int length) {
429445

430446
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
431447
boolean isExist(Object id) {
432-
String sql = String.format("select count(*) from %s ref where ref.%s = :id", voClass.getSimpleName(), voPrimaryKeyField.getName());
448+
String sql = String.format("select count(*) from %s ref where ref.%s = :id", voClass.getSimpleName(),
449+
voPrimaryKeyField.getName());
433450
TypedQuery<Long> q = getEntityManager().createQuery(sql, Long.class);
434451
q.setParameter("id", id);
435452
q.setMaxResults(1);
@@ -438,13 +455,25 @@ boolean isExist(Object id) {
438455
}
439456

440457
void installLifeCycleCallback(EntityEvent evt, EntityLifeCycleCallback l) {
441-
listeners.put(evt, l);
458+
List<EntityLifeCycleCallback> cbs = listeners.computeIfAbsent(evt, k -> new CopyOnWriteArrayList<>());
459+
if (!cbs.contains(l)) {
460+
cbs.add(l);
461+
}
462+
}
463+
464+
void uninstallLifeCycleCallback(EntityEvent evt, EntityLifeCycleCallback l) {
465+
List<EntityLifeCycleCallback> cbs = listeners.get(evt);
466+
if (cbs != null) {
467+
cbs.remove(l);
468+
}
442469
}
443470

444471
void fireLifeCycleEvent(EntityEvent evt, Object o) {
445-
EntityLifeCycleCallback cb = listeners.get(evt);
446-
if (cb != null) {
447-
cb.entityLifeCycleEvent(evt, o);
472+
List<EntityLifeCycleCallback> cbs = listeners.get(evt);
473+
if (cbs != null) {
474+
for (EntityLifeCycleCallback cb : cbs) {
475+
cb.entityLifeCycleEvent(evt, o);
476+
}
448477
}
449478
}
450479
}
@@ -491,7 +520,8 @@ public CriteriaBuilder getCriteriaBuilder() {
491520

492521
@Override
493522
public <T> SimpleQuery<T> createQuery(Class<T> entityClass) {
494-
assert entityClass.isAnnotationPresent(Entity.class) : entityClass.getName() + " is not annotated by JPA @Entity";
523+
assert entityClass.isAnnotationPresent(Entity.class)
524+
: entityClass.getName() + " is not annotated by JPA @Entity";
495525
return new SimpleQueryImpl<T>(entityClass);
496526
}
497527

@@ -530,7 +560,6 @@ public void removeByPrimaryKeys(Collection priKeys, Class entityClazz) {
530560
getEntityInfo(entityClazz).removeByPrimaryKeys(priKeys);
531561
}
532562

533-
534563
@Override
535564
public <T> T updateAndRefresh(T entity) {
536565
return (T) getEntityInfo(entity.getClass()).updateAndRefresh(entity);
@@ -636,7 +665,9 @@ public void entityForTranscationCallback(Operation op, Class<?>... entityClass)
636665
sb.append(c.getName()).append(",");
637666
}
638667

639-
String err = String.format("entityForTranscationCallback is called but transcation is not active. Did you forget adding @Transactional to method??? [operation: %s, entity classes: %s]", op, sb.toString());
668+
String err = String.format(
669+
"entityForTranscationCallback is called but transcation is not active. Did you forget adding @Transactional to method??? [operation: %s, entity classes: %s]",
670+
op, sb.toString());
640671
logger.warn(err);
641672
}
642673
}
@@ -668,7 +699,8 @@ public long generateSequenceNumber(Class<?> seqTable) {
668699
try {
669700
Field id = seqTable.getDeclaredField("id");
670701
if (id == null) {
671-
throw new CloudRuntimeException(String.format("sequence VO[%s] must have 'id' field", seqTable.getName()));
702+
throw new CloudRuntimeException(
703+
String.format("sequence VO[%s] must have 'id' field", seqTable.getName()));
672704
}
673705
Object vo = seqTable.newInstance();
674706
vo = persistAndRefresh(vo);
@@ -770,7 +802,7 @@ public void eoCleanup(Class VOClazz) {
770802
@Override
771803
@DeadlockAutoRestart
772804
public void eoCleanup(Class VOClazz, Object id) {
773-
if(id == null) {
805+
if (id == null) {
774806
throw new RuntimeException(String.format("Cleanup %s EO fail, id is null", VOClazz.getSimpleName()));
775807
}
776808

@@ -798,7 +830,7 @@ public boolean start() {
798830
}
799831

800832
private void buildEntityInfo() {
801-
BeanUtils.reflections.getTypesAnnotatedWith(Entity.class).forEach(clz-> {
833+
BeanUtils.reflections.getTypesAnnotatedWith(Entity.class).forEach(clz -> {
802834
entityInfoMap.put(clz, new EntityInfo(clz));
803835
});
804836
}
@@ -820,7 +852,8 @@ private void populateExtensions() {
820852
}
821853
}
822854

823-
for (SoftDeleteEntityByEOExtensionPoint ext : pluginRgty.getExtensionList(SoftDeleteEntityByEOExtensionPoint.class)) {
855+
for (SoftDeleteEntityByEOExtensionPoint ext : pluginRgty
856+
.getExtensionList(SoftDeleteEntityByEOExtensionPoint.class)) {
824857
for (Class eoClass : ext.getEOClassForSoftDeleteEntityExtension()) {
825858
List<SoftDeleteEntityByEOExtensionPoint> exts = softDeleteByEOExtensions.get(eoClass);
826859
if (exts == null) {
@@ -873,6 +906,20 @@ public void installEntityLifeCycleCallback(Class clz, EntityEvent evt, EntityLif
873906
}
874907
}
875908

909+
@Override
910+
public void uninstallEntityLifeCycleCallback(Class clz, EntityEvent evt, EntityLifeCycleCallback cb) {
911+
if (clz != null) {
912+
EntityInfo info = entityInfoMap.get(clz);
913+
if (info != null) {
914+
info.uninstallLifeCycleCallback(evt, cb);
915+
}
916+
} else {
917+
for (EntityInfo info : entityInfoMap.values()) {
918+
info.uninstallLifeCycleCallback(evt, cb);
919+
}
920+
}
921+
}
922+
876923
@Override
877924
public boolean stop() {
878925
return true;
@@ -881,7 +928,8 @@ public boolean stop() {
881928
void entityEvent(EntityEvent evt, Object entity) {
882929
EntityInfo info = entityInfoMap.get(entity.getClass());
883930
if (info == null) {
884-
logger.warn(String.format("cannot find EntityInfo for the class[%s], not entity events will be fired", entity.getClass()));
931+
logger.warn(String.format("cannot find EntityInfo for the class[%s], not entity events will be fired",
932+
entity.getClass()));
885933
return;
886934
}
887935

sdk/src/main/java/SourceClassMap.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,10 @@ public class SourceClassMap {
872872
put("org.zstack.zwatch.monitorgroup.entity.MonitorGroupTemplateRefInventory", "org.zstack.sdk.zwatch.monitorgroup.entity.MonitorGroupTemplateRefInventory");
873873
put("org.zstack.zwatch.monitorgroup.entity.MonitorGroupTemplateRefVO", "org.zstack.sdk.zwatch.monitorgroup.entity.MonitorGroupTemplateRefVO");
874874
put("org.zstack.zwatch.monitorgroup.entity.MonitorTemplateInventory", "org.zstack.sdk.zwatch.monitorgroup.entity.MonitorTemplateInventory");
875+
put("org.zstack.zwatch.resnotify.ResNotifySubscriptionInventory", "org.zstack.sdk.zwatch.resnotify.ResNotifySubscriptionInventory");
876+
put("org.zstack.zwatch.resnotify.ResNotifySubscriptionState", "org.zstack.sdk.zwatch.resnotify.ResNotifySubscriptionState");
877+
put("org.zstack.zwatch.resnotify.ResNotifyType", "org.zstack.sdk.zwatch.resnotify.ResNotifyType");
878+
put("org.zstack.zwatch.resnotify.ResNotifyWebhookRefInventory", "org.zstack.sdk.zwatch.resnotify.ResNotifyWebhookRefInventory");
875879
put("org.zstack.zwatch.ruleengine.ComparisonOperator", "org.zstack.sdk.zwatch.ruleengine.ComparisonOperator");
876880
put("org.zstack.zwatch.thirdparty.entity.SNSEndpointThirdpartyAlertHistoryInventory", "org.zstack.sdk.zwatch.thirdparty.entity.SNSEndpointThirdpartyAlertHistoryInventory");
877881
put("org.zstack.zwatch.thirdparty.entity.ThirdpartyOriginalAlertInventory", "org.zstack.sdk.zwatch.thirdparty.entity.ThirdpartyOriginalAlertInventory");
@@ -1748,6 +1752,10 @@ public class SourceClassMap {
17481752
put("org.zstack.sdk.zwatch.monitorgroup.entity.MonitorGroupTemplateRefInventory", "org.zstack.zwatch.monitorgroup.entity.MonitorGroupTemplateRefInventory");
17491753
put("org.zstack.sdk.zwatch.monitorgroup.entity.MonitorGroupTemplateRefVO", "org.zstack.zwatch.monitorgroup.entity.MonitorGroupTemplateRefVO");
17501754
put("org.zstack.sdk.zwatch.monitorgroup.entity.MonitorTemplateInventory", "org.zstack.zwatch.monitorgroup.entity.MonitorTemplateInventory");
1755+
put("org.zstack.sdk.zwatch.resnotify.ResNotifySubscriptionInventory", "org.zstack.zwatch.resnotify.ResNotifySubscriptionInventory");
1756+
put("org.zstack.sdk.zwatch.resnotify.ResNotifySubscriptionState", "org.zstack.zwatch.resnotify.ResNotifySubscriptionState");
1757+
put("org.zstack.sdk.zwatch.resnotify.ResNotifyType", "org.zstack.zwatch.resnotify.ResNotifyType");
1758+
put("org.zstack.sdk.zwatch.resnotify.ResNotifyWebhookRefInventory", "org.zstack.zwatch.resnotify.ResNotifyWebhookRefInventory");
17511759
put("org.zstack.sdk.zwatch.ruleengine.ComparisonOperator", "org.zstack.zwatch.ruleengine.ComparisonOperator");
17521760
put("org.zstack.sdk.zwatch.thirdparty.entity.SNSEndpointThirdpartyAlertHistoryInventory", "org.zstack.zwatch.thirdparty.entity.SNSEndpointThirdpartyAlertHistoryInventory");
17531761
put("org.zstack.sdk.zwatch.thirdparty.entity.ThirdpartyOriginalAlertInventory", "org.zstack.zwatch.thirdparty.entity.ThirdpartyOriginalAlertInventory");

0 commit comments

Comments
 (0)