Repository: gravitee-io/graviteeio-access-management
Pull request #1104 (2021-04-08 17:06:46)

feat(cockpit): monitoring data gathering
Closes gravitee-io/issues#5171


Patch:
diff --git a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-container/src/main/java/io/gravitee/am/gateway/node/GatewayNode.java b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-container/src/main/java/io/gravitee/am/gateway/node/GatewayNode.java
index 5018b2a579..f272ab7ba7 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-container/src/main/java/io/gravitee/am/gateway/node/GatewayNode.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-container/src/main/java/io/gravitee/am/gateway/node/GatewayNode.java
@@ -21,7 +21,9 @@ import io.gravitee.common.component.LifecycleComponent;
 import io.gravitee.node.container.AbstractNode;
 import io.gravitee.plugin.alert.AlertEventProducerManager;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author David BRASSELY (david.brassely at graviteesource.com)
@@ -50,4 +52,10 @@ public class GatewayNode extends AbstractNode {
 
         return components;
     }
+
+    @Override
+    public Map<String, Object> metadata() {
+        Map<String, Object> metadata = new HashMap<>();
+        return metadata;
+    }
 }
diff --git a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-container/src/main/java/io/gravitee/am/gateway/spring/StandaloneConfiguration.java b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-container/src/main/java/io/gravitee/am/gateway/spring/StandaloneConfiguration.java
index 7192accfbe..9f4abaf3c9 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-container/src/main/java/io/gravitee/am/gateway/spring/StandaloneConfiguration.java
+++ b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-container/src/main/java/io/gravitee/am/gateway/spring/StandaloneConfiguration.java
@@ -31,7 +31,6 @@ import io.gravitee.am.plugins.idp.spring.IdentityProviderConfiguration;
 import io.gravitee.am.plugins.policy.spring.PolicyConfiguration;
 import io.gravitee.am.plugins.protocol.spring.ProtocolConfiguration;
 import io.gravitee.am.plugins.reporter.spring.ReporterConfiguration;
-import io.gravitee.am.plugins.resource.core.ResourceConfigurationFactory;
 import io.gravitee.am.plugins.resource.spring.ResourceConfiguration;
 import io.gravitee.el.ExpressionLanguageInitializer;
 import io.gravitee.node.container.NodeFactory;
@@ -64,7 +63,8 @@ import org.springframework.context.annotation.Import;
         PolicyConfiguration.class,
         AlertPluginConfiguration.class,
         FactorConfiguration.class,
-        ResourceConfiguration.class
+        ResourceConfiguration.class,
+        FactorConfiguration.class
 })
 public class StandaloneConfiguration {
 
diff --git a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml
index 30d50fb5c0..6db276a37c 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml
+++ b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/pom.xml
@@ -209,22 +209,6 @@
             <scope>runtime</scope>
         </dependency>
 
-        <dependency>
-            <groupId>io.gravitee.node.services</groupId>
-            <artifactId>gravitee-node-services-healthcheck</artifactId>
-            <version>${gravitee-node.version}</version>
-            <type>zip</type>
-            <scope>runtime</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>io.gravitee.node.services</groupId>
-            <artifactId>gravitee-node-services-monitoring</artifactId>
-            <version>${gravitee-node.version}</version>
-            <type>zip</type>
-            <scope>runtime</scope>
-        </dependency>
-
         <dependency>
             <groupId>io.gravitee.am.reporter</groupId>
             <artifactId>gravitee-am-reporter-mongodb</artifactId>
@@ -524,20 +508,6 @@
                                     <type>zip</type>
                                 </artifactItem>
 
-                                <artifactItem>
-                                    <groupId>io.gravitee.node.services</groupId>
-                                    <artifactId>gravitee-node-services-healthcheck</artifactId>
-                                    <version>${gravitee-node.version}</version>
-                                    <type>zip</type>
-                                </artifactItem>
-
-                                <artifactItem>
-                                    <groupId>io.gravitee.node.services</groupId>
-                                    <artifactId>gravitee-node-services-monitoring</artifactId>
-                                    <version>${gravitee-node.version}</version>
-                                    <type>zip</type>
-                                </artifactItem>
-
                                 <!-- Reporters -->
                                 <artifactItem>
                                     <groupId>io.gravitee.am.reporter</groupId>
diff --git a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/src/main/resources/config/gravitee.yml b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/src/main/resources/config/gravitee.yml
index 6301f2a8ba..c67485dad6 100644
--- a/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/src/main/resources/config/gravitee.yml
+++ b/gravitee-am-gateway/gravitee-am-gateway-standalone/gravitee-am-gateway-standalone-distribution/src/main/resources/config/gravitee.yml
@@ -118,6 +118,12 @@ services:
     prometheus:
       enabled: true
 
+  # Gateway monitoring service.
+  # This service retrieves metrics like os / process / jvm metrics.
+  monitoring:
+    delay: 5000
+    unit: MILLISECONDS
+
 # OAuth2 repository is used to store OAuth2 tokens: access_token, refresh_token
 oauth2:
   type: mongodb
diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/InstallationCommandHandler.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/InstallationCommandHandler.java
index 2f5d3f4ae9..b0c234ad8c 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/InstallationCommandHandler.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/commands/InstallationCommandHandler.java
@@ -15,28 +15,19 @@
  */
 package io.gravitee.am.management.service.impl.commands;
 
-import io.gravitee.am.common.oidc.StandardClaims;
 import io.gravitee.am.model.Installation;
-import io.gravitee.am.model.ReferenceType;
 import io.gravitee.am.service.InstallationService;
-import io.gravitee.am.service.model.NewUser;
 import io.gravitee.cockpit.api.command.Command;
 import io.gravitee.cockpit.api.command.CommandHandler;
 import io.gravitee.cockpit.api.command.CommandStatus;
-import io.gravitee.cockpit.api.command.hello.HelloCommand;
 import io.gravitee.cockpit.api.command.installation.InstallationCommand;
 import io.gravitee.cockpit.api.command.installation.InstallationPayload;
 import io.gravitee.cockpit.api.command.installation.InstallationReply;
-import io.gravitee.cockpit.api.command.user.UserCommand;
-import io.gravitee.cockpit.api.command.user.UserPayload;
-import io.gravitee.cockpit.api.command.user.UserReply;
 import io.reactivex.Single;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 
-import java.util.HashMap;
-
 /**
  * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
  * @author GraviteeSource Team
diff --git a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml
index 1832a23bf2..b7fc79ea0d 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml
+++ b/gravitee-am-management-api/gravitee-am-management-api-standalone/gravitee-am-management-api-standalone-distribution/pom.xml
@@ -236,22 +236,6 @@
             <scope>runtime</scope>
         </dependency>
 
-        <dependency>
-            <groupId>io.gravitee.node.services</groupId>
-            <artifactId>gravitee-node-services-healthcheck</artifactId>
-            <version>${gravitee-node.version}</version>
-            <type>zip</type>
-            <scope>runtime</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>io.gravitee.node.services</groupId>
-            <artifactId>gravitee-node-services-monitoring</artifactId>
-            <version>${gravitee-node.version}</version>
-            <type>zip</type>
-            <scope>runtime</scope>
-        </dependency>
-
         <dependency>
             <groupId>io.gravitee.am.factor</groupId>
             <artifactId>gravitee-am-factor-otp</artifactId>
@@ -537,20 +521,6 @@
                                     <type>zip</type>
                                 </artifactItem>
 
-                                <artifactItem>
-                                    <groupId>io.gravitee.node.services</groupId>
-                                    <artifactId>gravitee-node-services-healthcheck</artifactId>
-                                    <version>${gravitee-node.version}</version>
-                                    <type>zip</type>
-                                </artifactItem>
-
-                                <artifactItem>
-                                    <groupId>io.gravitee.node.services</groupId>
-                                    <artifactId>gravitee-node-services-monitoring</artifactId>
-                                    <version>${gravitee-node.version}</version>
-                                    <type>zip</type>
-                                </artifactItem>
-
                                 <!-- Factors -->
                                 <artifactItem>
                                     <groupId>io.gravitee.am.factor</groupId>
diff --git a/gravitee-am-repository/gravitee-am-repository-api/pom.xml b/gravitee-am-repository/gravitee-am-repository-api/pom.xml
index 90a31fd5c8..fb30a75764 100644
--- a/gravitee-am-repository/gravitee-am-repository-api/pom.xml
+++ b/gravitee-am-repository/gravitee-am-repository-api/pom.xml
@@ -53,7 +53,6 @@
         <dependency>
             <groupId>io.gravitee.node</groupId>
             <artifactId>gravitee-node-api</artifactId>
-            <scope>provided</scope>
         </dependency>
 
         <!-- Spring -->
diff --git a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/JdbcNodeMonitoringRepository.java b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/JdbcNodeMonitoringRepository.java
new file mode 100644
index 0000000000..00f4b1b7f4
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/JdbcNodeMonitoringRepository.java
@@ -0,0 +1,87 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * 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 io.gravitee.am.repository.jdbc.management.api;
+
+import io.gravitee.am.repository.jdbc.management.AbstractJdbcRepository;
+import io.gravitee.am.repository.jdbc.management.api.model.JdbcMonitoring;
+import io.gravitee.am.repository.jdbc.management.api.spring.SpringNodeMonitoringRepository;
+import io.gravitee.node.api.Monitoring;
+import io.gravitee.node.api.NodeMonitoringRepository;
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import reactor.core.publisher.Mono;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+
+import static java.time.ZoneOffset.UTC;
+import static reactor.adapter.rxjava.RxJava2Adapter.monoToSingle;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Repository
+public class JdbcNodeMonitoringRepository extends AbstractJdbcRepository implements NodeMonitoringRepository {
+
+    @Autowired
+    protected SpringNodeMonitoringRepository nodeMonitoringRepository;
+
+    protected Monitoring toEntity(JdbcMonitoring entity) {
+        return mapper.map(entity, Monitoring.class);
+    }
+
+    protected JdbcMonitoring toJdbcEntity(Monitoring entity) {
+        return mapper.map(entity, JdbcMonitoring.class);
+    }
+
+    @Override
+    public Maybe<Monitoring> findByNodeIdAndType(String nodeId, String type) {
+        LOGGER.debug("findByNodeIdAndType({}, {})", nodeId, type);
+        return nodeMonitoringRepository.findByNodeIdAndType(nodeId, type)
+                .map(this::toEntity);
+    }
+
+    @Override
+    public Flowable<Monitoring> findByTypeAndTimeFrame(String type, long from, long to) {
+        LOGGER.debug("findByTypeAndTimeFrame({}, {}, {})", type, from, to);
+        return nodeMonitoringRepository.findByTypeAndTimeFrame(type,
+                LocalDateTime.ofInstant(Instant.ofEpochMilli(from), UTC),
+                LocalDateTime.ofInstant(Instant.ofEpochMilli(to), UTC))
+                .map(this::toEntity);
+    }
+
+    @Override
+    public Single<Monitoring> create(Monitoring monitoring) {
+        LOGGER.debug("Create Monitoring with id {}", monitoring.getId());
+        Mono<Integer> insertResult = dbClient.insert()
+                .into(JdbcMonitoring.class)
+                .using(toJdbcEntity(monitoring))
+                .fetch().rowsUpdated();
+
+        return monoToSingle(insertResult.map(result -> monitoring));
+    }
+
+    @Override
+    public Single<Monitoring> update(Monitoring monitoring) {
+        LOGGER.debug("Update monitoring with id '{}'", monitoring.getId());
+        return nodeMonitoringRepository.save(toJdbcEntity(monitoring))
+                .map(this::toEntity);
+    }
+}
diff --git a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/model/JdbcMonitoring.java b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/model/JdbcMonitoring.java
new file mode 100644
index 0000000000..efabff1b3e
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/model/JdbcMonitoring.java
@@ -0,0 +1,106 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * 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 io.gravitee.am.repository.jdbc.management.api.model;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.relational.core.mapping.Column;
+import org.springframework.data.relational.core.mapping.Table;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Table("node_monitoring")
+public class JdbcMonitoring {
+
+    @Id
+    private String id;
+
+    @Column("node_id")
+    private String nodeId;
+
+    private String type;
+
+    private String payload;
+
+    @Column("created_at")
+    private LocalDateTime createdAt;
+
+    @Column("evaluated_at")
+    private LocalDateTime evaluatedAt;
+
+    @Column("updated_at")
+    private LocalDateTime updatedAt;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getNodeId() {
+        return nodeId;
+    }
+
+    public void setNodeId(String nodeId) {
+        this.nodeId = nodeId;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getPayload() {
+        return payload;
+    }
+
+    public void setPayload(String payload) {
+        this.payload = payload;
+    }
+
+    public LocalDateTime getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(LocalDateTime createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    public LocalDateTime getUpdatedAt() {
+        return updatedAt;
+    }
+
+    public void setUpdatedAt(LocalDateTime updatedAt) {
+        this.updatedAt = updatedAt;
+    }
+
+    public LocalDateTime getEvaluatedAt() {
+        return evaluatedAt;
+    }
+
+    public void setEvaluatedAt(LocalDateTime evaluatedAt) {
+        this.evaluatedAt = evaluatedAt;
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/spring/SpringNodeMonitoringRepository.java b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/spring/SpringNodeMonitoringRepository.java
new file mode 100644
index 0000000000..19faa5722f
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/java/io/gravitee/am/repository/jdbc/management/api/spring/SpringNodeMonitoringRepository.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * 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 io.gravitee.am.repository.jdbc.management.api.spring;
+
+import io.gravitee.am.repository.jdbc.management.api.model.JdbcMonitoring;
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import org.springframework.data.r2dbc.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.data.repository.reactive.RxJava2CrudRepository;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface SpringNodeMonitoringRepository extends RxJava2CrudRepository<JdbcMonitoring, String> {
+
+    @Query("select * from node_monitoring m where m.node_id = :nodeId and m.type = :type")
+    Maybe<JdbcMonitoring> findByNodeIdAndType(@Param("nodeId") String nodeId, @Param("type") String type);
+
+    @Query("select * from node_monitoring m where m.type = :type and m.updated_at >= :from and m.updated_at <= :to")
+    Flowable<JdbcMonitoring> findByTypeAndTimeFrame(@Param("type") String type, @Param("from") LocalDateTime from, @Param("to") LocalDateTime to);
+}
diff --git a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/resources/liquibase/changelogs/v3_8_0/schema.yml b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/resources/liquibase/changelogs/v3_8_0/schema.yml
index df6aa159cf..26f5f8e1e5 100644
--- a/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/resources/liquibase/changelogs/v3_8_0/schema.yml
+++ b/gravitee-am-repository/gravitee-am-repository-jdbc/src/main/resources/liquibase/changelogs/v3_8_0/schema.yml
@@ -75,3 +75,31 @@ databaseChangeLog:
             indexName:  idx_webauthn_credentials_ref_aaguid
             tableName:  webauthn_credentials
             unique:  false
+
+        # node_monitoring
+        ########################
+        - createTable:
+            tableName: node_monitoring
+            columns:
+              - column: { name: id, type: nvarchar(64), constraints: { nullable: false } }
+              - column: { name: node_id, type: nvarchar(64), constraints: { nullable: false } }
+              - column: { name: type, type: nvarchar(64), constraints: { nullable: false } }
+              - column: { name: payload, type: nclob, constraints: { nullable: false } }
+              - column: { name: created_at, type: timestamp(6), constraints: { nullable: true } }
+              - column: { name: updated_at, type: timestamp(6), constraints: { nullable: true } }
+              - column: { name: evaluated_at, type: timestamp(6), constraints: { nullable: true } }
+
+        - addPrimaryKey:
+            constraintName: pk_node_monitoring
+            columnNames: id
+            tableName: node_monitoring
+
+        - createIndex:
+            columns:
+              - column:
+                  name: updated_at
+              - column:
+                  name: type
+            indexName: idx_node_monitoring_updated_at_type
+            tableName: node_monitoring
+            unique: false
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/AbstractManagementMongoRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/AbstractManagementMongoRepository.java
index f3c03b8c67..24c56e3178 100644
--- a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/AbstractManagementMongoRepository.java
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/AbstractManagementMongoRepository.java
@@ -66,7 +66,10 @@ public abstract class AbstractManagementMongoRepository extends AbstractMongoRep
                 return eq(name, ((Enum<?>) value).name());
             }
 
-            if(value instanceof Collection) {
+            if (value instanceof Collection) {
+                if (((Collection<?>) value).isEmpty()) {
+                    return null;
+                }
                 return in(name, (Collection<?>) value);
             }
 
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoNodeMonitoringRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoNodeMonitoringRepository.java
new file mode 100644
index 0000000000..17c46bcb4c
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoNodeMonitoringRepository.java
@@ -0,0 +1,115 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * 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 io.gravitee.am.repository.mongodb.management;
+
+import com.mongodb.reactivestreams.client.MongoCollection;
+import io.gravitee.am.repository.mongodb.management.internal.model.MonitoringMongo;
+import io.gravitee.node.api.Monitoring;
+import io.gravitee.node.api.NodeMonitoringRepository;
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import org.bson.Document;
+import org.bson.conversions.Bson;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static com.mongodb.client.model.Filters.*;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Component
+public class MongoNodeMonitoringRepository extends AbstractManagementMongoRepository implements NodeMonitoringRepository {
+
+    private static final String FIELD_NODE_ID = "nodeId";
+    private static final String FIELD_TYPE = "type";
+
+    private MongoCollection<MonitoringMongo> collection;
+
+    @PostConstruct
+    public void init() {
+        collection = mongoOperations.getCollection("node_monitoring", MonitoringMongo.class);
+        super.init(collection);
+        super.createIndex(collection, new Document(FIELD_UPDATED_AT, 1));
+    }
+
+    @Override
+    public Maybe<Monitoring> findByNodeIdAndType(String nodeId, String type) {
+        return Observable.fromPublisher(collection.find(and(eq(FIELD_NODE_ID, nodeId), eq(FIELD_TYPE, type))).first())
+                .firstElement()
+                .map(this::convert);
+    }
+
+    @Override
+    public Single<Monitoring> create(Monitoring monitoring) {
+        return Single.fromPublisher(collection.insertOne(convert(monitoring)))
+                .map(success -> monitoring);
+    }
+
+    @Override
+    public Single<Monitoring> update(Monitoring monitoring) {
+        return Single.fromPublisher(collection.replaceOne(eq(FIELD_ID, monitoring.getId()), convert(monitoring)))
+                .map(updateResult -> monitoring);
+    }
+
+    @Override
+    public Flowable<Monitoring> findByTypeAndTimeFrame(String type, long from, long to) {
+        List<Bson> filters = new ArrayList<>();
+        filters.add(eq(FIELD_TYPE, type));
+        filters.add(gte(FIELD_UPDATED_AT, new Date(from)));
+
+        if (to > from) {
+            filters.add(lte(FIELD_UPDATED_AT, new Date(to)));
+        }
+
+        return Flowable.fromPublisher(collection.find(and(filters))).map(this::convert);
+    }
+
+    private Monitoring convert(MonitoringMongo monitoringMongo) {
+
+        final Monitoring monitoring = new Monitoring();
+        monitoring.setId(monitoringMongo.getId());
+        monitoring.setNodeId(monitoringMongo.getNodeId());
+        monitoring.setType(monitoringMongo.getType());
+        monitoring.setEvaluatedAt(monitoringMongo.getEvaluatedAt());
+        monitoring.setCreatedAt(monitoringMongo.getCreatedAt());
+        monitoring.setUpdatedAt(monitoringMongo.getUpdatedAt());
+        monitoring.setPayload(monitoringMongo.getPayload());
+
+        return monitoring;
+    }
+
+    private MonitoringMongo convert(Monitoring monitoring) {
+
+        final MonitoringMongo monitoringMongo = new MonitoringMongo();
+        monitoringMongo.setId(monitoring.getId());
+        monitoringMongo.setNodeId(monitoring.getNodeId());
+        monitoringMongo.setType(monitoring.getType());
+        monitoringMongo.setEvaluatedAt(monitoring.getEvaluatedAt());
+        monitoringMongo.setCreatedAt(monitoring.getCreatedAt());
+        monitoringMongo.setUpdatedAt(monitoring.getUpdatedAt());
+        monitoringMongo.setPayload(monitoring.getPayload());
+
+        return monitoringMongo;
+    }
+}
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/MonitoringMongo.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/MonitoringMongo.java
new file mode 100644
index 0000000000..bc77fb04fc
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/MonitoringMongo.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * 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 io.gravitee.am.repository.mongodb.management.internal.model;
+
+import org.bson.codecs.pojo.annotations.BsonId;
+
+import java.util.Date;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class MonitoringMongo {
+
+    @BsonId
+    private String id;
+    private String nodeId;
+    private String type;
+    private String payload;
+    private Date createdAt;
+    private Date evaluatedAt;
+    private Date updatedAt;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getNodeId() {
+        return nodeId;
+    }
+
+    public void setNodeId(String nodeId) {
+        this.nodeId = nodeId;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getPayload() {
+        return payload;
+    }
+
+    public void setPayload(String payload) {
+        this.payload = payload;
+    }
+
+    public Date getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(Date createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    public Date getUpdatedAt() {
+        return updatedAt;
+    }
+
+    public void setUpdatedAt(Date updatedAt) {
+        this.updatedAt = updatedAt;
+    }
+
+    public Date getEvaluatedAt() {
+        return evaluatedAt;
+    }
+
+    public void setEvaluatedAt(Date evaluatedAt) {
+        this.evaluatedAt = evaluatedAt;
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/InstallationServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/InstallationServiceImpl.java
index e50e584f56..2db2edf620 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/InstallationServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/InstallationServiceImpl.java
@@ -82,15 +82,7 @@ public class InstallationServiceImpl implements InstallationService {
     @Override
     public Completable delete() {
         return this.installationRepository.find()
-                .switchIfEmpty(Single.error(new InstallationNotFoundException()))
-                .flatMapCompletable(installation -> installationRepository.delete(installation.getId()))
-                .onErrorResumeNext(ex -> {
-                    // installation already deleted, continue
-                    if (ex instanceof InstallationNotFoundException) {
-                        return Completable.complete();
-                    }
-                    return Completable.error(ex);
-                });
+                .flatMapCompletable(installation -> installationRepository.delete(installation.getId()));
     }
 
     private Single<Installation> createInternal() {
diff --git a/pom.xml b/pom.xml
index edf22ecb2f..f2a90b3799 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>io.gravitee</groupId>
         <artifactId>gravitee-parent</artifactId>
-        <version>19.2.3-SNAPSHOT</version>
+        <version>19.2.3</version>
     </parent>
 
     <groupId>io.gravitee.am</groupId>
@@ -64,15 +64,15 @@
     </modules>
 
     <properties>
-        <gravitee-parent.version>19.2.3-SNAPSHOT</gravitee-parent.version>
+        <gravitee-parent.version>19.2.3</gravitee-parent.version>
         <gravitee-common.version>1.16.2</gravitee-common.version>
         <gravitee-plugin.version>1.17.1</gravitee-plugin.version>
-        <gravitee-node.version>1.10.0</gravitee-node.version>
+        <gravitee-node.version>1.12.0</gravitee-node.version>
         <gravitee-reporter.version>1.17.1</gravitee-reporter.version>
         <gravitee-gateway-api.version>1.20.0</gravitee-gateway-api.version>
         <gravitee-expression-language.version>1.4.0</gravitee-expression-language.version>
         <gravitee-platform-repository-api.version>1.0.0</gravitee-platform-repository-api.version>
-        <gravitee-cockpit-api.version>1.3.0</gravitee-cockpit-api.version>
+        <gravitee-cockpit-api.version>1.4.0</gravitee-cockpit-api.version>
         <spring-security.version>5.3.1.RELEASE</spring-security.version>
         <nimbus.version>8.17</nimbus.version>
         <tink.version>1.5.0</tink.version>
@@ -106,7 +106,7 @@
         <gravitee-policy-ipfiltering.version>1.5.0</gravitee-policy-ipfiltering.version>
         <gravitee-policy-request-validation.version>1.10.0</gravitee-policy-request-validation.version>
         <gravitee-license-node.version>1.1.0</gravitee-license-node.version>
-        <gravitee-cockpit-connectors.version>1.2.0</gravitee-cockpit-connectors.version>
+        <gravitee-cockpit-connectors.version>1.3.0</gravitee-cockpit-connectors.version>
         <gravitee-notifier-webhook.version>1.0.6</gravitee-notifier-webhook.version>
         <gravitee-notifier-email.version>1.2.8</gravitee-notifier-email.version>
         <gravitee-alert-api.version>1.7.0</gravitee-alert-api.version>


Test patch:
diff --git a/gravitee-am-repository/gravitee-am-repository-jdbc/src/test/java/io/gravitee/am/repository/jdbc/common/JdbcRepositoriesTestInitializer.java b/gravitee-am-repository/gravitee-am-repository-jdbc/src/test/java/io/gravitee/am/repository/jdbc/common/JdbcRepositoriesTestInitializer.java
index 373e7fef65..1f2f2ce672 100644
--- a/gravitee-am-repository/gravitee-am-repository-jdbc/src/test/java/io/gravitee/am/repository/jdbc/common/JdbcRepositoriesTestInitializer.java
+++ b/gravitee-am-repository/gravitee-am-repository-jdbc/src/test/java/io/gravitee/am/repository/jdbc/common/JdbcRepositoriesTestInitializer.java
@@ -101,6 +101,7 @@ public class JdbcRepositoriesTestInitializer implements RepositoriesTestInitiali
         tables.add("alert_triggers");
         tables.add("alert_notifiers");
         tables.add("service_resources");
+        tables.add("node_monitoring");
 
         io.r2dbc.spi.Connection connection = Flowable.fromPublisher(connectionFactory.create()).blockingFirst();
         connection.beginTransaction();
diff --git a/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/management/api/NodeMonitoringRepositoryTest.java b/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/management/api/NodeMonitoringRepositoryTest.java
new file mode 100644
index 0000000000..7fede96068
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-tests/src/test/java/io/gravitee/am/repository/management/api/NodeMonitoringRepositoryTest.java
@@ -0,0 +1,146 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * 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 io.gravitee.am.repository.management.api;
+
+import io.gravitee.am.common.utils.RandomString;
+import io.gravitee.am.repository.management.AbstractManagementTest;
+import io.gravitee.node.api.Monitoring;
+import io.gravitee.node.api.NodeMonitoringRepository;
+import io.reactivex.observers.TestObserver;
+import io.reactivex.subscribers.TestSubscriber;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Date;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class NodeMonitoringRepositoryTest extends AbstractManagementTest {
+
+    private static final String NODE_ID = "node#1";
+    private static final String MONITORING_TYPE = "MONITOR";
+
+    @Autowired
+    private NodeMonitoringRepository nodeMonitoringRepository;
+
+    @Test
+    public void testCreate() {
+        Monitoring alertNotifier = buildMonitoring();
+        TestObserver<Monitoring> testObserver = nodeMonitoringRepository.create(alertNotifier).test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        testObserver.assertValue(monitoring -> monitoring.getId().equals(alertNotifier.getId()));
+    }
+
+    @Test
+    public void testUpdate() {
+        // create idp
+        Monitoring monitoring = buildMonitoring();
+        Monitoring monitoringCreated = nodeMonitoringRepository.create(monitoring).blockingGet();
+
+        // update idp
+        Monitoring updatedMonitoring = buildMonitoring();
+        updatedMonitoring.setId(monitoringCreated.getId());
+        updatedMonitoring.setPayload("updatedPayload");
+
+        TestObserver<Monitoring> testObserver = nodeMonitoringRepository.update(updatedMonitoring).test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        testObserver.assertValue(updated -> updated.getId().equals(updatedMonitoring.getId())
+                && updated.getPayload().equals(updatedMonitoring.getPayload()));
+    }
+
+    @Test
+    public void findByNodeIdAndType() {
+
+        Monitoring monitoringToCreate;
+
+        for (int i = 0; i < 10; i++) {
+            monitoringToCreate = buildMonitoring();
+            monitoringToCreate.setType((i % 2 == 0) ? MONITORING_TYPE : "HEALTH_CHECK");
+            nodeMonitoringRepository.create(monitoringToCreate).blockingGet();
+        }
+
+        monitoringToCreate = buildMonitoring();
+        monitoringToCreate.setNodeId(NODE_ID);
+        monitoringToCreate.setType(MONITORING_TYPE);
+        final Monitoring monitoringCreated = nodeMonitoringRepository.create(monitoringToCreate).blockingGet();
+
+        TestObserver<Monitoring> obs = nodeMonitoringRepository.findByNodeIdAndType(NODE_ID, MONITORING_TYPE).test();
+        obs.awaitTerminalEvent();
+        obs.assertComplete();
+        obs.assertNoErrors();
+        obs.assertValue(monitoring -> monitoring.getId().equals(monitoringCreated.getId()));
+    }
+
+    @Test
+    public void findByTypeAndTimeFrame() {
+
+        long from = LocalDateTime.of(1970, 12, 25, 10, 0, 0, 0).toInstant(ZoneOffset.UTC).toEpochMilli();
+        long to = LocalDateTime.of(1970, 12, 25, 11, 0, 0, 0).toInstant(ZoneOffset.UTC).toEpochMilli();
+        Monitoring monitoringToCreate;
+
+        // Create 10 monitoring objects.
+        for (int i = 0; i < 10; i++) {
+            monitoringToCreate = buildMonitoring();
+            if (i % 2 == 0) {
+                // Change updated date 1 out of 2.
+                monitoringToCreate.setUpdatedAt(new Date(from + 10000));
+            }
+
+            if (i == 8) {
+                // Change the type of the 9th, so it should be excluded from the results.
+                monitoringToCreate.setType("HEALTH_CHECK");
+            }
+
+            nodeMonitoringRepository.create(monitoringToCreate).blockingGet();
+        }
+
+        TestSubscriber<Monitoring> obs = nodeMonitoringRepository.findByTypeAndTimeFrame(MONITORING_TYPE, from, to).test();
+        obs.awaitTerminalEvent();
+        obs.assertValueCount(4);
+
+        obs.values().forEach(monitoring -> {
+            assertTrue(monitoring.getUpdatedAt().getTime() >= from
+                    && monitoring.getUpdatedAt().getTime() <= to
+                    && monitoring.getType().equals(MONITORING_TYPE));
+        });
+
+    }
+
+    private Monitoring buildMonitoring() {
+        Monitoring monitoring = new Monitoring();
+        monitoring.setId(RandomString.generate());
+        monitoring.setNodeId(RandomString.generate());
+        monitoring.setPayload("payload");
+        monitoring.setType(MONITORING_TYPE);
+        monitoring.setCreatedAt(new Date());
+        monitoring.setEvaluatedAt(new Date());
+        monitoring.setUpdatedAt(new Date());
+        return monitoring;
+    }
+}