Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions conf/db/upgrade/V5.5.12__schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,30 @@ WHERE `uuid` IN (
)
AND `metricName` = 'VRouterCPUAverageUsedUtilization';

-- ZSTAC-80472: Resource notification webhook tables
CREATE TABLE IF NOT EXISTS `zstack`.`ResNotifySubscriptionVO` (
`uuid` VARCHAR(32) NOT NULL UNIQUE,
`name` VARCHAR(255) DEFAULT NULL,
`description` VARCHAR(2048) DEFAULT NULL,
`resourceTypes` TEXT DEFAULT NULL,
`eventTypes` VARCHAR(256) DEFAULT NULL,
`type` VARCHAR(32) NOT NULL DEFAULT 'WEBHOOK',
`state` VARCHAR(32) NOT NULL DEFAULT 'Enabled',
`accountUuid` VARCHAR(32) DEFAULT NULL,
`lastOpDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`createDate` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`uuid`),
INDEX `idx_ResNotifySubscriptionVO_accountUuid` (`accountUuid`),
INDEX `idx_ResNotifySubscriptionVO_type_state` (`type`, `state`),
CONSTRAINT `fkResNotifySubscriptionVOResourceVO` FOREIGN KEY (`uuid`) REFERENCES `ResourceVO` (`uuid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `zstack`.`ResNotifyWebhookRefVO` (
`uuid` VARCHAR(32) NOT NULL UNIQUE,
`webhookUrl` TEXT NOT NULL,
`secret` VARCHAR(256) DEFAULT NULL,
`customHeaders` TEXT,
PRIMARY KEY (`uuid`),
CONSTRAINT `fk_ResNotifyWebhookRefVO_ResNotifySubscriptionVO`
FOREIGN KEY (`uuid`) REFERENCES `ResNotifySubscriptionVO`(`uuid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2 changes: 2 additions & 0 deletions core/src/main/java/org/zstack/core/db/DatabaseFacade.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,6 @@ public interface DatabaseFacade {
String getDbVersion();

void installEntityLifeCycleCallback(Class entityClass, EntityEvent evt, EntityLifeCycleCallback cb);

void uninstallEntityLifeCycleCallback(Class entityClass, EntityEvent evt, EntityLifeCycleCallback cb);
}
102 changes: 75 additions & 27 deletions core/src/main/java/org/zstack/core/db/DatabaseFacadeImpl.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.zstack.core.db;

import com.google.common.collect.Maps;
import java.util.concurrent.CopyOnWriteArrayList;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NestedExceptionUtils;
Expand Down Expand Up @@ -66,7 +68,7 @@ class EntityInfo {
Field eoSoftDeleteColumn;
Class eoClass;
Class voClass;
Map<EntityEvent, EntityLifeCycleCallback> listeners = new HashMap<EntityEvent, EntityLifeCycleCallback>();
Map<EntityEvent, List<EntityLifeCycleCallback>> listeners = Maps.newConcurrentMap();

EntityInfo(Class voClazz) {
voClass = voClazz;
Expand All @@ -82,12 +84,16 @@ class EntityInfo {
EO at = (EO) voClazz.getAnnotation(EO.class);
if (at != null) {
eoClass = at.EOClazz();
DebugUtils.Assert(eoClass != null, String.format("cannot find EO entity specified by VO entity[%s]", voClazz.getName()));
DebugUtils.Assert(eoClass != null,
String.format("cannot find EO entity specified by VO entity[%s]", voClazz.getName()));
eoPrimaryKeyField = FieldUtils.getAnnotatedField(Id.class, eoClass);
DebugUtils.Assert(eoPrimaryKeyField != null, String.format("cannot find primary key field(@Id annotated) in EO entity[%s]", eoClass.getName()));
DebugUtils.Assert(eoPrimaryKeyField != null, String
.format("cannot find primary key field(@Id annotated) in EO entity[%s]", eoClass.getName()));
eoPrimaryKeyField.setAccessible(true);
eoSoftDeleteColumn = FieldUtils.getField(at.softDeletedColumn(), eoClass);
DebugUtils.Assert(eoSoftDeleteColumn != null, String.format("cannot find soft delete column[%s] in EO entity[%s]", at.softDeletedColumn(), eoClass.getName()));
DebugUtils.Assert(eoSoftDeleteColumn != null,
String.format("cannot find soft delete column[%s] in EO entity[%s]", at.softDeletedColumn(),
eoClass.getName()));
eoSoftDeleteColumn.setAccessible(true);
}

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

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

final Class parent = voClass.getSuperclass();
if (!parent.isAnnotationPresent(Entity.class)) {
throw new CloudRuntimeException(String.format("class[%s] has annotation @PrimaryKeyJoinColumn but its parent class[%s] is not annotated by @Entity",
throw new CloudRuntimeException(String.format(
"class[%s] has annotation @PrimaryKeyJoinColumn but its parent class[%s] is not annotated by @Entity",
voClass, parent));
}

Expand Down Expand Up @@ -245,17 +254,21 @@ private void updateEO(Object entity, RuntimeException de) {
}

SQLIntegrityConstraintViolationException me = (SQLIntegrityConstraintViolationException) rootCause;
if (!(me.getErrorCode() == 1062 && "23000".equals(me.getSQLState()) && me.getMessage().contains("PRIMARY"))) {
if (!(me.getErrorCode() == 1062 && "23000".equals(me.getSQLState())
&& me.getMessage().contains("PRIMARY"))) {
throw de;
}

if (!hasEO()) {
throw de;
}

// at this point, the error is caused by a update tried on VO entity which has been soft deleted. This is mostly
// 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
// 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
// at this point, the error is caused by a update tried on VO entity which has
// been soft deleted. This is mostly
// 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
// 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

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

@Transactional
private void nativeSqlDelete(Collection ids) {
// native sql can avoid JPA cascades a deletion to parent entity when deleting a child entity
String sql = String.format("delete from %s where %s in (:ids)", voClass.getSimpleName(), voPrimaryKeyField.getName());
// native sql can avoid JPA cascades a deletion to parent entity when deleting a
// child entity
String sql = String.format("delete from %s where %s in (:ids)", voClass.getSimpleName(),
voPrimaryKeyField.getName());
Query q = getEntityManager().createNativeQuery(sql);
q.setParameter("ids", ids);
q.executeUpdate();
Expand Down Expand Up @@ -418,7 +433,8 @@ List listByPrimaryKeys(Collection ids, int offset, int length) {
sql = String.format("select e from %s e", voClass.getSimpleName());
query = getEntityManager().createQuery(sql, voClass);
} else {
sql = String.format("select e from %s e where e.%s in (:ids)", voClass.getSimpleName(), voPrimaryKeyField.getName());
sql = String.format("select e from %s e where e.%s in (:ids)", voClass.getSimpleName(),
voPrimaryKeyField.getName());
query = getEntityManager().createQuery(sql, voClass);
query.setParameter("ids", ids);
}
Expand All @@ -429,7 +445,8 @@ List listByPrimaryKeys(Collection ids, int offset, int length) {

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

void installLifeCycleCallback(EntityEvent evt, EntityLifeCycleCallback l) {
listeners.put(evt, l);
List<EntityLifeCycleCallback> cbs = listeners.computeIfAbsent(evt, k -> new CopyOnWriteArrayList<>());
if (!cbs.contains(l)) {
cbs.add(l);
}
}

void uninstallLifeCycleCallback(EntityEvent evt, EntityLifeCycleCallback l) {
List<EntityLifeCycleCallback> cbs = listeners.get(evt);
if (cbs != null) {
cbs.remove(l);
}
}

void fireLifeCycleEvent(EntityEvent evt, Object o) {
EntityLifeCycleCallback cb = listeners.get(evt);
if (cb != null) {
cb.entityLifeCycleEvent(evt, o);
List<EntityLifeCycleCallback> cbs = listeners.get(evt);
if (cbs != null) {
for (EntityLifeCycleCallback cb : cbs) {
cb.entityLifeCycleEvent(evt, o);
}
}
}
}
Expand Down Expand Up @@ -491,7 +520,8 @@ public CriteriaBuilder getCriteriaBuilder() {

@Override
public <T> SimpleQuery<T> createQuery(Class<T> entityClass) {
assert entityClass.isAnnotationPresent(Entity.class) : entityClass.getName() + " is not annotated by JPA @Entity";
assert entityClass.isAnnotationPresent(Entity.class)
: entityClass.getName() + " is not annotated by JPA @Entity";
return new SimpleQueryImpl<T>(entityClass);
}

Expand Down Expand Up @@ -530,7 +560,6 @@ public void removeByPrimaryKeys(Collection priKeys, Class entityClazz) {
getEntityInfo(entityClazz).removeByPrimaryKeys(priKeys);
}


@Override
public <T> T updateAndRefresh(T entity) {
return (T) getEntityInfo(entity.getClass()).updateAndRefresh(entity);
Expand Down Expand Up @@ -636,7 +665,9 @@ public void entityForTranscationCallback(Operation op, Class<?>... entityClass)
sb.append(c.getName()).append(",");
}

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());
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());
logger.warn(err);
}
}
Expand Down Expand Up @@ -668,7 +699,8 @@ public long generateSequenceNumber(Class<?> seqTable) {
try {
Field id = seqTable.getDeclaredField("id");
if (id == null) {
throw new CloudRuntimeException(String.format("sequence VO[%s] must have 'id' field", seqTable.getName()));
throw new CloudRuntimeException(
String.format("sequence VO[%s] must have 'id' field", seqTable.getName()));
}
Object vo = seqTable.newInstance();
vo = persistAndRefresh(vo);
Expand Down Expand Up @@ -770,7 +802,7 @@ public void eoCleanup(Class VOClazz) {
@Override
@DeadlockAutoRestart
public void eoCleanup(Class VOClazz, Object id) {
if(id == null) {
if (id == null) {
throw new RuntimeException(String.format("Cleanup %s EO fail, id is null", VOClazz.getSimpleName()));
}

Expand Down Expand Up @@ -798,7 +830,7 @@ public boolean start() {
}

private void buildEntityInfo() {
BeanUtils.reflections.getTypesAnnotatedWith(Entity.class).forEach(clz-> {
BeanUtils.reflections.getTypesAnnotatedWith(Entity.class).forEach(clz -> {
entityInfoMap.put(clz, new EntityInfo(clz));
});
}
Expand All @@ -820,7 +852,8 @@ private void populateExtensions() {
}
}

for (SoftDeleteEntityByEOExtensionPoint ext : pluginRgty.getExtensionList(SoftDeleteEntityByEOExtensionPoint.class)) {
for (SoftDeleteEntityByEOExtensionPoint ext : pluginRgty
.getExtensionList(SoftDeleteEntityByEOExtensionPoint.class)) {
for (Class eoClass : ext.getEOClassForSoftDeleteEntityExtension()) {
List<SoftDeleteEntityByEOExtensionPoint> exts = softDeleteByEOExtensions.get(eoClass);
if (exts == null) {
Expand Down Expand Up @@ -873,6 +906,20 @@ public void installEntityLifeCycleCallback(Class clz, EntityEvent evt, EntityLif
}
}

@Override
public void uninstallEntityLifeCycleCallback(Class clz, EntityEvent evt, EntityLifeCycleCallback cb) {
if (clz != null) {
EntityInfo info = entityInfoMap.get(clz);
if (info != null) {
info.uninstallLifeCycleCallback(evt, cb);
}
} else {
for (EntityInfo info : entityInfoMap.values()) {
info.uninstallLifeCycleCallback(evt, cb);
}
}
}

@Override
public boolean stop() {
return true;
Expand All @@ -881,7 +928,8 @@ public boolean stop() {
void entityEvent(EntityEvent evt, Object entity) {
EntityInfo info = entityInfoMap.get(entity.getClass());
if (info == null) {
logger.warn(String.format("cannot find EntityInfo for the class[%s], not entity events will be fired", entity.getClass()));
logger.warn(String.format("cannot find EntityInfo for the class[%s], not entity events will be fired",
entity.getClass()));
return;
}

Expand Down
8 changes: 8 additions & 0 deletions sdk/src/main/java/SourceClassMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,10 @@ public class SourceClassMap {
put("org.zstack.zwatch.monitorgroup.entity.MonitorGroupTemplateRefInventory", "org.zstack.sdk.zwatch.monitorgroup.entity.MonitorGroupTemplateRefInventory");
put("org.zstack.zwatch.monitorgroup.entity.MonitorGroupTemplateRefVO", "org.zstack.sdk.zwatch.monitorgroup.entity.MonitorGroupTemplateRefVO");
put("org.zstack.zwatch.monitorgroup.entity.MonitorTemplateInventory", "org.zstack.sdk.zwatch.monitorgroup.entity.MonitorTemplateInventory");
put("org.zstack.zwatch.resnotify.ResNotifySubscriptionInventory", "org.zstack.sdk.zwatch.resnotify.ResNotifySubscriptionInventory");
put("org.zstack.zwatch.resnotify.ResNotifySubscriptionState", "org.zstack.sdk.zwatch.resnotify.ResNotifySubscriptionState");
put("org.zstack.zwatch.resnotify.ResNotifyType", "org.zstack.sdk.zwatch.resnotify.ResNotifyType");
put("org.zstack.zwatch.resnotify.ResNotifyWebhookRefInventory", "org.zstack.sdk.zwatch.resnotify.ResNotifyWebhookRefInventory");
put("org.zstack.zwatch.ruleengine.ComparisonOperator", "org.zstack.sdk.zwatch.ruleengine.ComparisonOperator");
put("org.zstack.zwatch.thirdparty.entity.SNSEndpointThirdpartyAlertHistoryInventory", "org.zstack.sdk.zwatch.thirdparty.entity.SNSEndpointThirdpartyAlertHistoryInventory");
put("org.zstack.zwatch.thirdparty.entity.ThirdpartyOriginalAlertInventory", "org.zstack.sdk.zwatch.thirdparty.entity.ThirdpartyOriginalAlertInventory");
Expand Down Expand Up @@ -1748,6 +1752,10 @@ public class SourceClassMap {
put("org.zstack.sdk.zwatch.monitorgroup.entity.MonitorGroupTemplateRefInventory", "org.zstack.zwatch.monitorgroup.entity.MonitorGroupTemplateRefInventory");
put("org.zstack.sdk.zwatch.monitorgroup.entity.MonitorGroupTemplateRefVO", "org.zstack.zwatch.monitorgroup.entity.MonitorGroupTemplateRefVO");
put("org.zstack.sdk.zwatch.monitorgroup.entity.MonitorTemplateInventory", "org.zstack.zwatch.monitorgroup.entity.MonitorTemplateInventory");
put("org.zstack.sdk.zwatch.resnotify.ResNotifySubscriptionInventory", "org.zstack.zwatch.resnotify.ResNotifySubscriptionInventory");
put("org.zstack.sdk.zwatch.resnotify.ResNotifySubscriptionState", "org.zstack.zwatch.resnotify.ResNotifySubscriptionState");
put("org.zstack.sdk.zwatch.resnotify.ResNotifyType", "org.zstack.zwatch.resnotify.ResNotifyType");
put("org.zstack.sdk.zwatch.resnotify.ResNotifyWebhookRefInventory", "org.zstack.zwatch.resnotify.ResNotifyWebhookRefInventory");
put("org.zstack.sdk.zwatch.ruleengine.ComparisonOperator", "org.zstack.zwatch.ruleengine.ComparisonOperator");
put("org.zstack.sdk.zwatch.thirdparty.entity.SNSEndpointThirdpartyAlertHistoryInventory", "org.zstack.zwatch.thirdparty.entity.SNSEndpointThirdpartyAlertHistoryInventory");
put("org.zstack.sdk.zwatch.thirdparty.entity.ThirdpartyOriginalAlertInventory", "org.zstack.zwatch.thirdparty.entity.ThirdpartyOriginalAlertInventory");
Expand Down
Loading