Skip to content

Commit

Permalink
Enrich catalog operation data (#559)
Browse files Browse the repository at this point in the history
* Add catalog_operation_id to operation

* Add new operation and registry enrichment functions

* Add catalog_operation_id validation to changeset

* Apply change to operation server code

* Display catalog information in operation endpoints

* Add test operations registry
  • Loading branch information
arbulu89 authored Jan 30, 2025
1 parent 22c0419 commit e2a61cb
Show file tree
Hide file tree
Showing 18 changed files with 333 additions and 31 deletions.
18 changes: 16 additions & 2 deletions lib/wanda/operations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ defmodule Wanda.Operations do
StepReport
}

alias Wanda.Operations.Catalog.Registry

require Wanda.Operations.Enums.Result, as: Result
require Wanda.Operations.Enums.Status, as: Status

Expand All @@ -22,12 +24,14 @@ defmodule Wanda.Operations do
If the operation already exists, it will be returned.
"""
@spec create_operation!(String.t(), String.t(), [OperationTarget.t()]) :: Operation.t()
def create_operation!(operation_id, group_id, targets) do
@spec create_operation!(String.t(), String.t(), String.t(), [OperationTarget.t()]) ::
Operation.t()
def create_operation!(operation_id, group_id, catalog_operation_id, targets) do
%Operation{}
|> Operation.changeset(%{
operation_id: operation_id,
group_id: group_id,
catalog_operation_id: catalog_operation_id,
status: Status.running(),
result: Result.not_executed(),
targets: Enum.map(targets, &Map.from_struct/1)
Expand All @@ -43,6 +47,16 @@ defmodule Wanda.Operations do
Repo.get!(Operation, operation_id)
end

@doc """
Enrich operation adding catalog_operation field
"""
@spec enrich_operation!(Operation.t()) :: Operation.t()
def enrich_operation!(%Operation{catalog_operation_id: catalog_operation_id} = operation) do
catalog_operation = Registry.get_operation!(catalog_operation_id)

%Operation{operation | catalog_operation: catalog_operation}
end

@doc """
Get a paginated list of operations.
Expand Down
1 change: 1 addition & 0 deletions lib/wanda/operations/catalog/operation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule Wanda.Operations.Catalog.Operation do

alias Wanda.Operations.Catalog.Step

@derive Jason.Encoder
defstruct [
:id,
:name,
Expand Down
16 changes: 14 additions & 2 deletions lib/wanda/operations/catalog/registry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,29 @@ defmodule Wanda.Operations.Catalog.Registry do
"""
@spec get_operations() :: [Operation.t()]
def get_operations do
Map.values(@registry)
Map.values(registry())
end

@doc """
Get an operation by id
"""
@spec get_operation(String.t()) :: {:ok, Operation.t()} | {:error, :operation_not_found}
def get_operation(id) do
case Map.get(@registry, id) do
case Map.get(registry(), id) do
nil -> {:error, :operation_not_found}
operation -> {:ok, operation}
end
end

@doc """
Get an operation by id, erroring out if the entry doesn't exist
"""
@spec get_operation!(String.t()) :: Operation.t()
def get_operation!(id) do
Map.fetch!(registry(), id)
end

defp registry do
Application.get_env(:wanda, :operations_registry, @registry)
end
end
1 change: 1 addition & 0 deletions lib/wanda/operations/catalog/step.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule Wanda.Operations.Catalog.Step do

@default_timeout 5 * 60 * 1_000

@derive Jason.Encoder
defstruct [
:name,
:operator,
Expand Down
18 changes: 17 additions & 1 deletion lib/wanda/operations/operation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ defmodule Wanda.Operations.Operation do

import Ecto.Changeset

alias Wanda.Operations.Catalog.Registry

require Wanda.Operations.Enums.Result, as: Result
require Wanda.Operations.Enums.Status, as: Status

@type t :: %__MODULE__{}

@fields ~w(operation_id group_id result status agent_reports started_at updated_at completed_at)a
@fields ~w(operation_id group_id result status agent_reports catalog_operation_id started_at updated_at completed_at)a
@target_fields ~w(agent_id arguments)a

@required_fields ~w(operation_id group_id result status)a
Expand All @@ -35,6 +37,9 @@ defmodule Wanda.Operations.Operation do

field :agent_reports, {:array, :map}

field :catalog_operation_id, :string
field :catalog_operation, :map, virtual: true

field :completed_at, :utc_datetime_usec
timestamps(type: :utc_datetime_usec, inserted_at: :started_at)
end
Expand All @@ -45,11 +50,22 @@ defmodule Wanda.Operations.Operation do
|> cast(params, @fields)
|> cast_embed(:targets, with: &target_changeset/2, required: true)
|> validate_required(@required_fields)
|> validate_change(:catalog_operation_id, &validate_catalog_operation_id/2)
end

defp target_changeset(target, params) do
target
|> cast(params, @target_fields)
|> validate_required(@targets_required_fields)
end

defp validate_catalog_operation_id(:catalog_operation_id, catalog_operation_id) do
case Registry.get_operation(catalog_operation_id) do
{:error, :operation_not_found} ->
[catalog_operation_id: "not found"]

_ ->
[]
end
end
end
7 changes: 5 additions & 2 deletions lib/wanda/operations/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,16 @@ defmodule Wanda.Operations.Server do
%State{
operation_id: operation_id,
group_id: group_id,
targets: targets
targets: targets,
operation: %Operation{
id: catalog_operation_id
}
} = state
) do
engine = EvaluationEngine.new()
new_state = initialize_report_results(state)

Operations.create_operation!(operation_id, group_id, targets)
Operations.create_operation!(operation_id, group_id, catalog_operation_id, targets)

{:noreply, %State{new_state | engine: engine}, {:continue, :execute_step}}
end
Expand Down
8 changes: 6 additions & 2 deletions lib/wanda_web/controllers/v1/operation_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ defmodule WandaWeb.V1.OperationController do

def index(conn, params) do
operations = Operations.list_operations(params)
enriched_operations = Enum.map(operations, &Operations.enrich_operation!(&1))

render(conn, operations: operations)
render(conn, operations: enriched_operations)
end

operation :show,
Expand All @@ -62,7 +63,10 @@ defmodule WandaWeb.V1.OperationController do
]

def show(conn, %{id: operation_id}) do
operation = Operations.get_operation!(operation_id)
operation =
operation_id
|> Operations.get_operation!()
|> Operations.enrich_operation!()

render(conn, operation: operation)
end
Expand Down
28 changes: 27 additions & 1 deletion lib/wanda_web/controllers/v1/operation_json.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ defmodule WandaWeb.V1.OperationJSON do
status: status,
targets: targets,
agent_reports: agent_reports,
catalog_operation_id: catalog_operation_id,
catalog_operation: %{
name: name,
description: description,
steps: steps
},
started_at: started_at,
updated_at: updated_at,
completed_at: completed_at
Expand All @@ -29,10 +35,30 @@ defmodule WandaWeb.V1.OperationJSON do
result: result,
status: status,
targets: targets,
agent_reports: agent_reports,
agent_reports: map_agent_reports(agent_reports, steps),
operation: catalog_operation_id,
name: name,
description: description,
started_at: started_at,
updated_at: updated_at,
completed_at: completed_at
}
end

defp map_agent_reports(agent_reports, steps) do
agent_reports
|> Enum.with_index()
|> Enum.map(fn {%{"agents" => agents}, index} ->
%{name: name, timeout: timeout, operator: operator, predicate: predicate} =
Enum.at(steps, index)

%{
agents: agents,
name: name,
timeout: timeout,
operator: operator,
predicate: predicate
}
end)
end
end
3 changes: 3 additions & 0 deletions lib/wanda_web/schemas/v1/operation/operation_response.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ defmodule WandaWeb.Schemas.V1.Operation.OperationResponse do
enum: Result.values(),
description: "Aggregated result of the operation, unknown for running ones"
},
name: %Schema{type: :string, description: "Operation name"},
description: %Schema{type: :string, description: "Operation description"},
operation: %Schema{type: :string, description: "Executed operation"},
targets: %Schema{type: :array, items: OperationTarget},
agent_reports: %Schema{type: :array, nullable: true, items: StepReport},
started_at: %Schema{
Expand Down
7 changes: 5 additions & 2 deletions lib/wanda_web/schemas/v1/operation/step_report.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ defmodule WandaWeb.Schemas.V1.Operation.StepReport do
type: :object,
additionalProperties: false,
properties: %{
step_number: %Schema{type: :integer, description: "Step number"},
name: %Schema{type: :string, description: "Operation step tname"},
operator: %Schema{type: :string, description: "Operation step operator"},
predicate: %Schema{type: :string, description: "Operation step predicate"},
timeout: %Schema{type: :integer, description: "Operation step timeout"},
agents: %Schema{type: :array, items: AgentReport}
},
required: [:step_number, :agents]
required: [:name, :operator, :predicate, :timeout, :agents]
},
struct?: false
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule Wanda.Repo.Migrations.AddCatalogOperationIdToOperation do
use Ecto.Migration

def change do
alter table(:operations) do
add :catalog_operation_id, :string, null: false
end
end
end
3 changes: 3 additions & 0 deletions test/support/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,15 @@ defmodule Wanda.Factory do
%CatalogOperation{
id: UUID.uuid4(),
name: Faker.StarWars.character(),
description: Faker.StarWars.quote(),
required_args: [],
steps: build_list(2, :operation_step)
}
end

def operation_step_factory do
%Step{
name: Faker.StarWars.character(),
operator: Faker.StarWars.planet(),
predicate: "*",
timeout: 10_000
Expand All @@ -245,6 +247,7 @@ defmodule Wanda.Factory do
%Operation{
operation_id: UUID.uuid4(),
group_id: UUID.uuid4(),
catalog_operation_id: "testoperation@v1",
result: OpeartionResult.not_executed(),
status: OpeartionStatus.running(),
targets: targets,
Expand Down
23 changes: 23 additions & 0 deletions test/support/operations/test_registry.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule Wanda.Operations.Catalog.TestRegistry do
@moduledoc false

def test_registry do
%{
"testoperation@v1" => %Wanda.Operations.Catalog.Operation{
id: "testoperation@v1",
name: "Test operation",
description: """
A test operation.
""",
required_args: ["arg"],
steps: [
%Wanda.Operations.Catalog.Step{
name: "First step",
operator: "test@v1",
predicate: "*"
}
]
}
}
end
end
17 changes: 16 additions & 1 deletion test/wanda/operations/catalog/registry_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ defmodule Wanda.Operations.Catalog.RegistryTest do
Registry
}

describe "operations" do
describe "get_operations/0" do
test "should return all existing operations" do
Enum.each(Registry.get_operations(), fn op ->
assert %Operation{} = op
end)
end
end

describe "get_operation/1" do
test "should return operation by id" do
assert {:ok, %Operation{id: "saptuneapplysolution@v1"}} =
Registry.get_operation("saptuneapplysolution@v1")
Expand All @@ -23,4 +25,17 @@ defmodule Wanda.Operations.Catalog.RegistryTest do
Registry.get_operation("somenastyoperation")
end
end

describe "get_operation!/1" do
test "should directly return operation by id" do
assert %Operation{id: "saptuneapplysolution@v1"} =
Registry.get_operation!("saptuneapplysolution@v1")
end

test "should bang if operation does not exist" do
assert_raise KeyError, fn ->
Registry.get_operation!("somenastyoperation")
end
end
end
end
Loading

0 comments on commit e2a61cb

Please sign in to comment.