Skip to content
Merged
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
43 changes: 31 additions & 12 deletions lib/diffo/provider/assigner/assigner.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,26 @@ defmodule Diffo.Provider.Assigner do
alias Diffo.Provider.AssignableCharacteristic
alias Diffo.Provider.AssignmentRelationship

@assignable_resource_states [:installing, :operating]
@assignable_service_states [:feasibilityChecked, :reserved, :inactive, :active, :suspended]

@doc """
The resource lifecycle states from which an instance may make assignments.
"""
def assignable_resource_states, do: @assignable_resource_states

@doc """
The service lifecycle states from which an instance may make assignments.
"""
def assignable_service_states, do: @assignable_service_states

@doc """
Assign a thing using the pool declared via `pools do` on the instance module.
The thing name is looked up from the pool declaration.
"""
def assign(result, changeset, pool_name)
when is_struct(result) and is_struct(changeset, Ash.Changeset) and is_atom(pool_name) do
with :ok <- check_lifecycle(result) do
with :ok <- assignable_state?(result) do
case result.__struct__.pool(pool_name) do
nil -> {:error, "pool #{pool_name} not declared on #{result.__struct__}"}
pool -> assign(result, changeset, pool_name, pool.thing)
Expand Down Expand Up @@ -61,17 +74,23 @@ defmodule Diffo.Provider.Assigner do
end
end

defp check_lifecycle(%{type: :resource, resource_state: state}) when state != :operating,
do:
{:error, "cannot assign: resource lifecycle state is #{inspect(state)}, must be :operating"}

defp check_lifecycle(%{type: :service, service_state: state})
when state not in [:active, :inactive],
do:
{:error,
"cannot assign: service state is #{inspect(state)}, must be :active or :inactive"}

defp check_lifecycle(_), do: :ok
@doc """
Returns `:ok` if the instance is in a lifecycle state that permits assignment,
otherwise `{:error, reason}`.
"""
def assignable_state?(%{type: :resource, resource_state: state})
when state not in @assignable_resource_states,
do:
{:error,
"cannot assign: resource lifecycle state is #{inspect(state)}, must be one of #{inspect(@assignable_resource_states)}"}

def assignable_state?(%{type: :service, service_state: state})
when state not in @assignable_service_states,
do:
{:error,
"cannot assign: service state is #{inspect(state)}, must be one of #{inspect(@assignable_service_states)}"}

def assignable_state?(_), do: :ok

defp create_assignment(result, pool, thing, value, assignee_id, alias_name)
when is_struct(result) and is_atom(pool) and is_atom(thing) and is_integer(value) and
Expand Down
87 changes: 87 additions & 0 deletions test/provider/extension/assigner_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule Diffo.Provider.Extension.AssignerTest do
@moduledoc false
use ExUnit.Case, async: true
@moduletag :domain_extended
alias Diffo.Provider.Assigner
alias Diffo.Provider.Specification
alias Diffo.Provider.Characteristic
alias Diffo.Provider.Assignment
Expand All @@ -19,6 +20,52 @@ defmodule Diffo.Provider.Extension.AssignerTest do
on_exit(&AshNeo4j.Sandbox.rollback/0)
end

# Issue #168 — broadened lifecycle policy. Service-side now covers the full
# committed lifecycle (excludes :initial, :cancelled, :terminated); resource
# side now allows :installing in addition to :operating.
describe "assignable_state?/1 (#168)" do
test "resource: :operating is permitted" do
assert :ok = Assigner.assignable_state?(%{type: :resource, resource_state: :operating})
end

test "resource: :installing is permitted" do
assert :ok = Assigner.assignable_state?(%{type: :resource, resource_state: :installing})
end

test "resource: :planning is rejected" do
assert {:error, msg} =
Assigner.assignable_state?(%{type: :resource, resource_state: :planning})

assert msg =~ ":planning"
end

test "resource: :retiring is rejected" do
assert {:error, _} =
Assigner.assignable_state?(%{type: :resource, resource_state: :retiring})
end

test "service: committed lifecycle states are permitted" do
for state <- [:feasibilityChecked, :reserved, :inactive, :active, :suspended] do
assert :ok = Assigner.assignable_state?(%{type: :service, service_state: state}),
"expected service_state #{inspect(state)} to be assignable"
end
end

test "service: :initial is rejected" do
assert {:error, msg} =
Assigner.assignable_state?(%{type: :service, service_state: :initial})

assert msg =~ ":initial"
end

test "service: terminal states are rejected" do
for state <- [:cancelled, :terminated] do
assert {:error, _} = Assigner.assignable_state?(%{type: :service, service_state: state}),
"expected service_state #{inspect(state)} to be rejected"
end
end
end

describe "build card" do
@tag :card
test "create a card" do
Expand Down Expand Up @@ -213,5 +260,45 @@ defmodule Diffo.Provider.Extension.AssignerTest do
assert encoding ==
~s({\"id\":\"#{card.id}",\"href\":\"resourceInventoryManagement/v4/resource/#{card.id}",\"category\":\"Network Resource\",\"description\":\"A Card Resource Instance\",\"resourceSpecification\":{\"id\":\"cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"href\":\"resourceCatalogManagement/v4/resourceSpecification/cd29956f-6c68-44cc-bf54-705eb8d2f754\",\"name\":\"card\",\"version\":\"v1.0.0\"},\"lifecycleState\":\"operating\"})
end

test "auto assign port to resource in :installing state (#168)" do
{:ok, assignee} = Parties.build_shelf_with_installer()

{:ok, card} = Servo.build_card(%{})

updates = [
card: [family: :ISAM, model: "EBLT48", technology: :adsl2Plus],
ports: [first: 1, last: 48, assignable_type: "ADSL2+"]
]

{:ok, card} = Servo.define_card(card, %{characteristic_value_updates: updates})
{:ok, card} = Servo.lifecycle_card(card, %{resource_state: :installing})

{:ok, card} =
Servo.assign_port(card, %{
assignment: %Assignment{assignee_id: assignee.id, operation: :auto_assign}
})

assert length(card.assignments) == 1
end

test "assign rejected while resource is in :planning state (#168)" do
{:ok, assignee} = Parties.build_shelf_with_installer()

{:ok, card} = Servo.build_card(%{})

updates = [
card: [family: :ISAM, model: "EBLT48", technology: :adsl2Plus],
ports: [first: 1, last: 48, assignable_type: "ADSL2+"]
]

{:ok, card} = Servo.define_card(card, %{characteristic_value_updates: updates})
{:ok, card} = Servo.lifecycle_card(card, %{resource_state: :planning})

assert {:error, _} =
Servo.assign_port(card, %{
assignment: %Assignment{assignee_id: assignee.id, operation: :auto_assign}
})
end
end
end
Loading