From 91f5768f0a197d5d013e3cd167862164681ba5b0 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Mon, 10 Jan 2022 19:12:07 +0100 Subject: [PATCH 1/6] Host aggregate emits HostDetailsUpdated event if the host is already registered and new details are discovered --- .../hosts/events/host_details_updated.ex | 17 +++++++ lib/tronto/monitoring/domain/hosts/host.ex | 49 ++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 lib/tronto/monitoring/domain/hosts/events/host_details_updated.ex diff --git a/lib/tronto/monitoring/domain/hosts/events/host_details_updated.ex b/lib/tronto/monitoring/domain/hosts/events/host_details_updated.ex new file mode 100644 index 0000000000..b307b9ebb8 --- /dev/null +++ b/lib/tronto/monitoring/domain/hosts/events/host_details_updated.ex @@ -0,0 +1,17 @@ +defmodule Tronto.Monitoring.Domain.Events.HostDetailsUpdated do + @moduledoc """ + This event is emitted when host details are updated. + """ + + use TypedStruct + + @derive Jason.Encoder + typedstruct do + @typedoc "HostDetailsUpdated event" + + field :id_host, String.t(), enforce: true + field :hostname, String.t(), enforce: true + field :ip_addresses, [String.t()], enforce: true + field :agent_version, String.t(), enforce: true + end +end diff --git a/lib/tronto/monitoring/domain/hosts/host.ex b/lib/tronto/monitoring/domain/hosts/host.ex index 90c4d83aea..7ab55271d7 100644 --- a/lib/tronto/monitoring/domain/hosts/host.ex +++ b/lib/tronto/monitoring/domain/hosts/host.ex @@ -11,6 +11,7 @@ defmodule Tronto.Monitoring.Domain.Host do alias Tronto.Monitoring.Domain.Events.{ HeartbeatFailed, HeartbeatSucceded, + HostDetailsUpdated, HostRegistered } @@ -49,13 +50,39 @@ defmodule Tronto.Monitoring.Domain.Host do } end + # Host exists but details didn't change def execute( - %Host{id_host: _}, - %RegisterHost{} + %Host{ + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + }, + %RegisterHost{ + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + } ) do [] end + def execute( + %Host{}, + %RegisterHost{ + id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + } + ) do + %HostDetailsUpdated{ + id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + } + end + # Heartbeat received def execute( %Host{id_host: nil}, @@ -107,6 +134,24 @@ defmodule Tronto.Monitoring.Domain.Host do } end + def apply( + %Host{} = host, + %HostDetailsUpdated{ + id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + } + ) do + %Host{ + host + | id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + } + end + def apply( %Host{} = host, %HeartbeatSucceded{id_host: id_host} From 3f29d24b2dd890437fc40eb9b114488ad2fa38d7 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Mon, 10 Jan 2022 19:40:17 +0100 Subject: [PATCH 2/6] Project HostUpdated event --- .../monitoring/projectors/host_projector.ex | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/lib/tronto/monitoring/projectors/host_projector.ex b/lib/tronto/monitoring/projectors/host_projector.ex index bc124ee59b..a3f4bf9dac 100644 --- a/lib/tronto/monitoring/projectors/host_projector.ex +++ b/lib/tronto/monitoring/projectors/host_projector.ex @@ -11,6 +11,7 @@ defmodule Tronto.Monitoring.HostProjector do alias Tronto.Monitoring.Domain.Events.{ HeartbeatFailed, HeartbeatSucceded, + HostDetailsUpdated, HostRegistered } @@ -41,6 +42,27 @@ defmodule Tronto.Monitoring.HostProjector do end ) + project( + %HostDetailsUpdated{ + id_host: id, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + }, + fn multi -> + changeset = + HostReadModel + |> Repo.get(id) + |> HostReadModel.changeset(%{ + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + }) + + Ecto.Multi.update(multi, :host, changeset) + end + ) + project( %HeartbeatSucceded{id_host: id}, fn multi -> @@ -79,30 +101,38 @@ defmodule Tronto.Monitoring.HostProjector do end def after_update( - %HeartbeatSucceded{id_host: id_host}, + %HostDetailsUpdated{}, + _, + %{host: host} + ) do + TrontoWeb.Endpoint.broadcast("hosts:notifications", "host_details_updated", host) + end + + def after_update( + %HeartbeatSucceded{}, _, - %{host: %HostReadModel{hostname: hostname}} + %{host: %HostReadModel{id: id, hostname: hostname}} ) do TrontoWeb.Endpoint.broadcast( "hosts:notifications", "heartbeat_succeded", %{ - id_host: id_host, + id: id, hostname: hostname } ) end def after_update( - %HeartbeatFailed{id_host: id_host}, + %HeartbeatFailed{}, _, - %{host: %HostReadModel{hostname: hostname}} + %{host: %HostReadModel{id: id, hostname: hostname}} ) do TrontoWeb.Endpoint.broadcast( "hosts:notifications", "heartbeat_failed", %{ - id_host: id_host, + id: id, hostname: hostname } ) From 6dda77db5f91e4d4c9740082ae081e4cf3bb33bd Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Mon, 10 Jan 2022 19:40:40 +0100 Subject: [PATCH 3/6] Add redux saga --- assets/js/state/hosts.js | 15 ++++++++++++--- assets/js/state/index.js | 4 ++++ assets/js/state/sagas/index.js | 12 ++++++++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/assets/js/state/hosts.js b/assets/js/state/hosts.js index 9525130d77..66290c3287 100644 --- a/assets/js/state/hosts.js +++ b/assets/js/state/hosts.js @@ -16,9 +16,18 @@ export const hostsListSlice = createSlice({ appendHost: (state, action) => { state.hosts = [...state.hosts, action.payload]; }, + updateHost: (state, action) => { + state.hosts = state.hosts.map((host) => { + if (host.id === action.payload.id) { + console.log(action.payload); + host = { ...host, ...action.payload } + } + return host; + }); + }, setHeartbeatPassing: (state, action) => { state.hosts = state.hosts.map((host) => { - if (host.id === action.payload.id_host) { + if (host.id === action.payload.id) { host.heartbeat = "passing"; } return host; @@ -26,7 +35,7 @@ export const hostsListSlice = createSlice({ }, setHeartbeatCritical: (state, action) => { state.hosts = state.hosts.map((host) => { - if (host.id === action.payload.id_host) { + if (host.id === action.payload.id) { host.heartbeat = "critical"; } return host; @@ -41,7 +50,7 @@ export const hostsListSlice = createSlice({ }, }); -export const { setHosts, appendHost, startLoading, stopLoading, setHeartbeatPassing, setHeartbeatCritical } = +export const { setHosts, appendHost, updateHost, startLoading, stopLoading, setHeartbeatPassing, setHeartbeatCritical } = hostsListSlice.actions; export default hostsListSlice.reducer; diff --git a/assets/js/state/index.js b/assets/js/state/index.js index 9c1b5d216b..6ddaceef24 100644 --- a/assets/js/state/index.js +++ b/assets/js/state/index.js @@ -26,6 +26,10 @@ const processChannelEvents = (store) => { store.dispatch({ type: 'HOST_REGISTERED', payload }) ); + channel.on('host_details_updated', (payload) => + store.dispatch({ type: 'HOST_DETAILS_UPDATED', payload }) +); + channel.on('heartbeat_succeded', (payload) => store.dispatch({ type: 'HEARTBEAT_SUCCEDED', payload }) ); diff --git a/assets/js/state/sagas/index.js b/assets/js/state/sagas/index.js index 478b868f56..692604d546 100644 --- a/assets/js/state/sagas/index.js +++ b/assets/js/state/sagas/index.js @@ -1,7 +1,7 @@ import { get } from 'axios'; import { put, all, call, takeEvery } from 'redux-saga/effects'; -import { appendHost, setHosts, startLoading, stopLoading, setHeartbeatPassing, setHeartbeatCritical } +import { appendHost, setHosts, updateHost, startLoading, stopLoading, setHeartbeatPassing, setHeartbeatCritical } from '../hosts'; import { watchNotifications } from './notifications'; @@ -31,6 +31,14 @@ function* watchHostRegistered() { yield takeEvery('HOST_REGISTERED', hostRegistered); } +function* hostDetailsUpdated({ payload }) { + yield put(updateHost(payload)); +} + +function* watchHostDetailsUpdated() { + yield takeEvery('HOST_DETAILS_UPDATED', hostDetailsUpdated); +} + function* heartbeatSucceded({ payload }) { yield put(setHeartbeatPassing(payload)); yield put( @@ -60,5 +68,5 @@ function* watchHeartbeatFailed() { } export default function* rootSaga() { - yield all([initialDataFetch(), watchHostRegistered(), watchHeartbeatSucceded(), watchHeartbeatFailed(), watchNotifications()]); + yield all([initialDataFetch(), watchHostRegistered(), watchHostDetailsUpdated(), watchHeartbeatSucceded(), watchHeartbeatFailed(), watchNotifications()]); } From 31bcebaf6c850965db99df340f1df6c0476be860 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Mon, 10 Jan 2022 19:40:58 +0100 Subject: [PATCH 4/6] Fix pattern match in host controller --- lib/tronto_web/controllers/host_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tronto_web/controllers/host_controller.ex b/lib/tronto_web/controllers/host_controller.ex index 531deddc97..b4b2353351 100644 --- a/lib/tronto_web/controllers/host_controller.ex +++ b/lib/tronto_web/controllers/host_controller.ex @@ -18,7 +18,7 @@ defmodule TrontoWeb.HostController do |> put_status(:accepted) |> json(%{}) - {:error, reason} -> + {:error, _, reason, _} -> conn |> put_status(:bad_request) |> json(%{error: reason}) From 79a53214e770f2e494a8fa164072bfec88c4787b Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Mon, 10 Jan 2022 20:11:35 +0100 Subject: [PATCH 5/6] Remove console log --- assets/js/state/hosts.js | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/js/state/hosts.js b/assets/js/state/hosts.js index 66290c3287..56726aa2de 100644 --- a/assets/js/state/hosts.js +++ b/assets/js/state/hosts.js @@ -19,7 +19,6 @@ export const hostsListSlice = createSlice({ updateHost: (state, action) => { state.hosts = state.hosts.map((host) => { if (host.id === action.payload.id) { - console.log(action.payload); host = { ...host, ...action.payload } } return host; From 84e1c9271081643c0db071d69d58df9c470419f1 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Mon, 10 Jan 2022 20:21:09 +0100 Subject: [PATCH 6/6] Add tests --- .../monitoring/domain/host/host_test.exs | 89 +++++++++++++++---- 1 file changed, 74 insertions(+), 15 deletions(-) diff --git a/test/tronto/monitoring/domain/host/host_test.exs b/test/tronto/monitoring/domain/host/host_test.exs index 777009a927..1f10ccc544 100644 --- a/test/tronto/monitoring/domain/host/host_test.exs +++ b/test/tronto/monitoring/domain/host/host_test.exs @@ -9,6 +9,7 @@ defmodule Tronto.Monitoring.HostTest do alias Tronto.Monitoring.Domain.Events.{ HeartbeatFailed, HeartbeatSucceded, + HostDetailsUpdated, HostRegistered } @@ -53,27 +54,85 @@ defmodule Tronto.Monitoring.HostTest do ) end - test "should not register a host if it is already registered" do + test "should update host details if it is already registered" do id_host = Faker.UUID.v4() + new_hostname = Faker.StarWars.character() + new_ip_addresses = [Faker.Internet.ip_v4_address()] + new_agent_version = Faker.Internet.slug() + + initial_events = [ + %HostRegistered{ + id_host: Faker.UUID.v4(), + hostname: Faker.StarWars.character(), + ip_addresses: [Faker.Internet.ip_v4_address()], + agent_version: Faker.Internet.slug(), + heartbeat: :unknown + } + ] + + commands = [ + RegisterHost.new!( + id_host: id_host, + hostname: new_hostname, + ip_addresses: new_ip_addresses, + agent_version: new_agent_version + ) + ] assert_events( + initial_events, + commands, [ - %HostRegistered{ + %HostDetailsUpdated{ id_host: id_host, - hostname: Faker.StarWars.character(), - ip_addresses: [Faker.Internet.ip_v4_address()], - agent_version: Faker.Internet.slug(), - heartbeat: :unknown + hostname: new_hostname, + ip_addresses: new_ip_addresses, + agent_version: new_agent_version } - ], - [ - RegisterHost.new!( - id_host: id_host, - hostname: Faker.StarWars.character(), - ip_addresses: [Faker.Internet.ip_v4_address()], - agent_version: Faker.Internet.slug() - ) - ], + ] + ) + + assert_state( + initial_events, + commands, + %Host{ + id_host: id_host, + hostname: new_hostname, + ip_addresses: new_ip_addresses, + agent_version: new_agent_version, + heartbeat: :unknown + } + ) + end + + test "should not update host details if the same details were already registered" do + id_host = Faker.UUID.v4() + hostname = Faker.StarWars.character() + ip_addresses = [Faker.Internet.ip_v4_address()] + agent_version = Faker.Internet.slug() + + initial_events = [ + %HostRegistered{ + id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version, + heartbeat: :unknown + } + ] + + commands = [ + RegisterHost.new!( + id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + ) + ] + + assert_events( + initial_events, + commands, [] ) end