thingsboard-aplcache

Details

diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
index f6c62be..6691670 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
@@ -16,6 +16,8 @@
 package org.thingsboard.server.common.data.alarm;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import org.thingsboard.server.common.data.BaseData;
 import org.thingsboard.server.common.data.id.AssetId;
@@ -26,6 +28,8 @@ import org.thingsboard.server.common.data.id.TenantId;
  * Created by ashvayka on 11.05.17.
  */
 @Data
+@Builder
+@AllArgsConstructor
 public class Alarm extends BaseData<AlarmId> {
 
     private TenantId tenantId;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/AbstractModelDao.java b/dao/src/main/java/org/thingsboard/server/dao/AbstractModelDao.java
index 01346b0..72ee9a2 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/AbstractModelDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/AbstractModelDao.java
@@ -161,9 +161,18 @@ public abstract class AbstractModelDao<T extends BaseEntity<?>> extends Abstract
         return getSession().execute(delete);
     }
 
-
     public List<T> find() {
         log.debug("Get all entities from column family {}", getColumnFamilyName());
         return findListByStatement(QueryBuilder.select().all().from(getColumnFamilyName()).setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()));
     }
+
+    protected static <T> Function<BaseEntity<T>, T> toDataFunction() {
+        return new Function<BaseEntity<T>, T>() {
+            @Nullable
+            @Override
+            public T apply(@Nullable BaseEntity<T> entity) {
+                return entity != null ? entity.toData() : null;
+            }
+        };
+    }
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
index 83286ac..e5383b6 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
@@ -22,6 +22,8 @@ import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.dao.Dao;
 import org.thingsboard.server.dao.model.AlarmEntity;
 
+import java.util.UUID;
+
 /**
  * Created by ashvayka on 11.05.17.
  */
@@ -29,6 +31,8 @@ public interface AlarmDao extends Dao<AlarmEntity> {
 
     ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
 
+    ListenableFuture<Alarm> findAlarmByIdAsync(UUID key);
+
     AlarmEntity save(Alarm alarm);
 
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java
index a6f6ee7..ba7e630 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java
@@ -1,12 +1,12 @@
 /**
  * Copyright © 2016-2017 The Thingsboard Authors
- *
+ * <p>
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,6 +15,11 @@
  */
 package org.thingsboard.server.dao.alarm;
 
+import com.datastax.driver.core.querybuilder.Ordering;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.datastax.driver.core.querybuilder.Select;
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
@@ -23,8 +28,16 @@ import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.dao.AbstractModelDao;
 import org.thingsboard.server.dao.model.AlarmEntity;
+import org.thingsboard.server.dao.model.BaseEntity;
+import org.thingsboard.server.dao.model.ModelConstants;
 
-import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME;
+import javax.annotation.Nullable;
+import java.util.Optional;
+import java.util.UUID;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+import static org.thingsboard.server.dao.model.ModelConstants.*;
 
 @Component
 @Slf4j
@@ -48,6 +61,23 @@ public class AlarmDaoImpl extends AbstractModelDao<AlarmEntity> implements Alarm
 
     @Override
     public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
-        return null;
+        Select select = select().from(ALARM_COLUMN_FAMILY_NAME);
+        Select.Where query = select.where();
+        query.and(eq(ALARM_TENANT_ID_PROPERTY, tenantId.getId()));
+        query.and(eq(ALARM_ORIGINATOR_ID_PROPERTY, originator.getId()));
+        query.and(eq(ALARM_ORIGINATOR_TYPE_PROPERTY, originator.getEntityType()));
+        query.and(eq(ALARM_TYPE_PROPERTY, type));
+        query.limit(1);
+        query.orderBy(QueryBuilder.asc(ModelConstants.ALARM_TYPE_PROPERTY), QueryBuilder.desc(ModelConstants.ID_PROPERTY));
+        return Futures.transform(findOneByStatementAsync(query), toDataFunction());
+    }
+
+    @Override
+    public ListenableFuture<Alarm> findAlarmByIdAsync(UUID key) {
+        log.debug("Get alarm by id {}", key);
+        Select.Where query = select().from(ALARM_BY_ID_VIEW_NAME).where(eq(ModelConstants.ID_PROPERTY, key));
+        query.limit(1);
+        log.trace("Execute query {}", query);
+        return Futures.transform(findOneByStatementAsync(query), toDataFunction());
     }
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
index a3daafc..6508db2 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
@@ -16,9 +16,11 @@
 package org.thingsboard.server.dao.alarm;
 
 import com.google.common.util.concurrent.ListenableFuture;
+import org.thingsboard.server.common.data.Device;
 import org.thingsboard.server.common.data.alarm.Alarm;
 import org.thingsboard.server.common.data.alarm.AlarmId;
 import org.thingsboard.server.common.data.alarm.AlarmQuery;
+import org.thingsboard.server.common.data.id.DeviceId;
 import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.page.TimePageData;
 
@@ -37,6 +39,8 @@ public interface AlarmService {
 
     ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long ackTs);
 
+    ListenableFuture<Alarm> findAlarmById(AlarmId alarmId);
+
     ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query);
 
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
index b9df342..120e6b7 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
@@ -1,12 +1,12 @@
 /**
  * Copyright © 2016-2017 The Thingsboard Authors
- *
+ * <p>
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -91,6 +91,9 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService 
             if (alarm.getStartTs() == 0L) {
                 alarm.setStartTs(System.currentTimeMillis());
             }
+            if (alarm.getEndTs() == 0L) {
+                alarm.setEndTs(alarm.getStartTs());
+            }
             Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get();
             if (existing == null || existing.getStatus().isCleared()) {
                 log.debug("New Alarm : {}", alarm);
@@ -117,11 +120,10 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService 
     @Override
     public ListenableFuture<Boolean> updateAlarm(Alarm update) {
         alarmDataValidator.validate(update);
-        return getAndUpdate(update.getId(), new Function<AlarmEntity, Boolean>() {
+        return getAndUpdate(update.getId(), new Function<Alarm, Boolean>() {
             @Nullable
             @Override
-            public Boolean apply(@Nullable AlarmEntity entity) {
-                Alarm alarm = getData(entity);
+            public Boolean apply(@Nullable Alarm alarm) {
                 if (alarm == null) {
                     return false;
                 } else {
@@ -139,11 +141,10 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService 
 
     @Override
     public ListenableFuture<Boolean> ackAlarm(AlarmId alarmId, long ackTime) {
-        return getAndUpdate(alarmId, new Function<AlarmEntity, Boolean>() {
+        return getAndUpdate(alarmId, new Function<Alarm, Boolean>() {
             @Nullable
             @Override
-            public Boolean apply(@Nullable AlarmEntity entity) {
-                Alarm alarm = getData(entity);
+            public Boolean apply(@Nullable Alarm alarm) {
                 if (alarm == null || alarm.getStatus().isAck()) {
                     return false;
                 } else {
@@ -161,11 +162,10 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService 
 
     @Override
     public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long clearTime) {
-        return getAndUpdate(alarmId, new Function<AlarmEntity, Boolean>() {
+        return getAndUpdate(alarmId, new Function<Alarm, Boolean>() {
             @Nullable
             @Override
-            public Boolean apply(@Nullable AlarmEntity entity) {
-                Alarm alarm = getData(entity);
+            public Boolean apply(@Nullable Alarm alarm) {
                 if (alarm == null || alarm.getStatus().isCleared()) {
                     return false;
                 } else {
@@ -182,6 +182,13 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService 
     }
 
     @Override
+    public ListenableFuture<Alarm> findAlarmById(AlarmId alarmId) {
+        log.trace("Executing findAlarmById [{}]", alarmId);
+        validateId(alarmId, "Incorrect alarmId " + alarmId);
+        return alarmDao.findAlarmByIdAsync(alarmId.getId());
+    }
+
+    @Override
     public ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query) {
         return null;
     }
@@ -230,9 +237,9 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService 
         }
     }
 
-    private ListenableFuture<Boolean> getAndUpdate(AlarmId alarmId, Function<AlarmEntity, Boolean> function) {
+    private ListenableFuture<Boolean> getAndUpdate(AlarmId alarmId, Function<Alarm, Boolean> function) {
         validateId(alarmId, "Alarm id should be specified!");
-        ListenableFuture<AlarmEntity> entity = alarmDao.findByIdAsync(alarmId.getId());
+        ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(alarmId.getId());
         return Futures.transform(entity, function);
     }
 
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/AlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/AlarmEntity.java
index 7dba381..62cd08a 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/AlarmEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/AlarmEntity.java
@@ -125,6 +125,94 @@ public final class AlarmEntity implements BaseEntity<Alarm> {
         this.tenantId = tenantId;
     }
 
+    public UUID getOriginatorId() {
+        return originatorId;
+    }
+
+    public void setOriginatorId(UUID originatorId) {
+        this.originatorId = originatorId;
+    }
+
+    public EntityType getOriginatorType() {
+        return originatorType;
+    }
+
+    public void setOriginatorType(EntityType originatorType) {
+        this.originatorType = originatorType;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public AlarmSeverity getSeverity() {
+        return severity;
+    }
+
+    public void setSeverity(AlarmSeverity severity) {
+        this.severity = severity;
+    }
+
+    public AlarmStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(AlarmStatus status) {
+        this.status = status;
+    }
+
+    public Long getStartTs() {
+        return startTs;
+    }
+
+    public void setStartTs(Long startTs) {
+        this.startTs = startTs;
+    }
+
+    public Long getEndTs() {
+        return endTs;
+    }
+
+    public void setEndTs(Long endTs) {
+        this.endTs = endTs;
+    }
+
+    public Long getAckTs() {
+        return ackTs;
+    }
+
+    public void setAckTs(Long ackTs) {
+        this.ackTs = ackTs;
+    }
+
+    public Long getClearTs() {
+        return clearTs;
+    }
+
+    public void setClearTs(Long clearTs) {
+        this.clearTs = clearTs;
+    }
+
+    public JsonNode getDetails() {
+        return details;
+    }
+
+    public void setDetails(JsonNode details) {
+        this.details = details;
+    }
+
+    public Boolean getPropagate() {
+        return propagate;
+    }
+
+    public void setPropagate(Boolean propagate) {
+        this.propagate = propagate;
+    }
+
     @Override
     public Alarm toData() {
         Alarm alarm = new Alarm(new AlarmId(id));
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
index 6a002b0..2efbd5d 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
@@ -157,6 +157,8 @@ public class ModelConstants {
     public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts";
     public static final String ALARM_PROPAGATE_PROPERTY = "propagate";
 
+    public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id";
+
     /**
      * Cassandra entity relation constants.
      */
diff --git a/dao/src/main/resources/schema.cql b/dao/src/main/resources/schema.cql
index 797e4a1..b86031b 100644
--- a/dao/src/main/resources/schema.cql
+++ b/dao/src/main/resources/schema.cql
@@ -250,7 +250,15 @@ CREATE TABLE IF NOT EXISTS thingsboard.alarm (
 	details text,
 	propagate boolean,
 	PRIMARY KEY ((tenant_id, originator_id, originator_type), type, id)
-);
+) WITH CLUSTERING ORDER BY ( type ASC, id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.alarm_by_id AS
+    SELECT *
+    from thingsboard.alarm
+    WHERE tenant_id IS NOT NULL AND originator_id IS NOT NULL AND originator_type IS NOT NULL AND type IS NOT NULL
+    AND type IS NOT NULL AND id IS NOT NULL
+    PRIMARY KEY (id, tenant_id, originator_id, originator_type, type)
+    WITH CLUSTERING ORDER BY ( tenant_id ASC, originator_id ASC, originator_type ASC, type ASC);
 
 CREATE TABLE IF NOT EXISTS thingsboard.relation (
 	from_id timeuuid,
diff --git a/dao/src/test/java/org/thingsboard/server/dao/DaoTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/DaoTestSuite.java
index b150259..82f9439 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/DaoTestSuite.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/DaoTestSuite.java
@@ -25,12 +25,13 @@ import java.util.Arrays;
 
 @RunWith(ClasspathSuite.class)
 @ClassnameFilters({
-        "org.thingsboard.server.dao.service.*Test",
-        "org.thingsboard.server.dao.kv.*Test",
-        "org.thingsboard.server.dao.plugin.*Test",
-        "org.thingsboard.server.dao.rule.*Test",
-        "org.thingsboard.server.dao.attributes.*Test",
-        "org.thingsboard.server.dao.timeseries.*Test"
+        "org.thingsboard.server.dao.service.AlarmServiceTest",
+//        "org.thingsboard.server.dao.service.*Test",
+//        "org.thingsboard.server.dao.kv.*Test",
+//        "org.thingsboard.server.dao.plugin.*Test",
+//        "org.thingsboard.server.dao.rule.*Test",
+//        "org.thingsboard.server.dao.attributes.*Test",
+//        "org.thingsboard.server.dao.timeseries.*Test"
 })
 public class DaoTestSuite {
 
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
index 903207c..d724d7f 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
@@ -32,6 +32,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.context.support.AnnotationConfigContextLoader;
 import org.thingsboard.server.common.data.BaseData;
 import org.thingsboard.server.common.data.Event;
+import org.thingsboard.server.common.data.alarm.AlarmStatus;
 import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.id.UUIDBased;
@@ -40,6 +41,7 @@ import org.thingsboard.server.common.data.plugin.ComponentScope;
 import org.thingsboard.server.common.data.plugin.ComponentType;
 import org.thingsboard.server.common.data.plugin.PluginMetaData;
 import org.thingsboard.server.common.data.rule.RuleMetaData;
+import org.thingsboard.server.dao.alarm.AlarmService;
 import org.thingsboard.server.dao.component.ComponentDescriptorService;
 import org.thingsboard.server.dao.customer.CustomerService;
 import org.thingsboard.server.dao.dashboard.DashboardService;
@@ -115,6 +117,9 @@ public abstract class AbstractServiceTest {
     protected RelationService relationService;
 
     @Autowired
+    protected AlarmService alarmService;
+
+    @Autowired
     private ComponentDescriptorService componentDescriptorService;
 
     class IdComparator<D extends BaseData<? extends UUIDBased>> implements Comparator<D> {
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java
new file mode 100644
index 0000000..8b90521
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service;
+
+import com.datastax.driver.core.utils.UUIDs;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmSeverity;
+import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.id.AssetId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.relation.EntityRelationsQuery;
+import org.thingsboard.server.dao.relation.EntitySearchDirection;
+import org.thingsboard.server.dao.relation.EntityTypeFilter;
+import org.thingsboard.server.dao.relation.RelationsSearchParameters;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+public class AlarmServiceTest extends AbstractServiceTest {
+
+    public static final String TEST_ALARM = "TEST_ALARM";
+    private TenantId tenantId;
+
+    @Before
+    public void before() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        Tenant savedTenant = tenantService.saveTenant(tenant);
+        Assert.assertNotNull(savedTenant);
+        tenantId = savedTenant.getId();
+    }
+
+    @After
+    public void after() {
+        tenantService.deleteTenant(tenantId);
+    }
+
+
+    @Test
+    public void testSaveAndFetchAlarm() throws ExecutionException, InterruptedException {
+        AssetId parentId = new AssetId(UUIDs.timeBased());
+        AssetId childId = new AssetId(UUIDs.timeBased());
+
+        EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
+
+        Assert.assertTrue(relationService.saveRelation(relation).get());
+
+        long ts = System.currentTimeMillis();
+        Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId)
+                .type(TEST_ALARM)
+                .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
+                .startTs(ts).build();
+
+        Alarm created = alarmService.createOrUpdateAlarm(alarm);
+
+        Assert.assertNotNull(created);
+        Assert.assertNotNull(created.getId());
+        Assert.assertNotNull(created.getOriginator());
+        Assert.assertNotNull(created.getSeverity());
+        Assert.assertNotNull(created.getStatus());
+
+        Assert.assertEquals(tenantId, created.getTenantId());
+        Assert.assertEquals(childId, created.getOriginator());
+        Assert.assertEquals(TEST_ALARM, created.getType());
+        Assert.assertEquals(AlarmSeverity.CRITICAL, created.getSeverity());
+        Assert.assertEquals(AlarmStatus.ACTIVE_UNACK, created.getStatus());
+        Assert.assertEquals(ts, created.getStartTs());
+        Assert.assertEquals(ts, created.getEndTs());
+        Assert.assertEquals(0L, created.getAckTs());
+        Assert.assertEquals(0L, created.getClearTs());
+
+        Alarm fetched = alarmService.findAlarmById(created.getId()).get();
+        Assert.assertEquals(created, fetched);
+    }
+}