Powered by AppSignal & Oban Pro

Diffo Example - NBN Domain

diffo_example_nbn.livemd

Diffo Example - NBN Domain

Mix.install(
  [
    # {:diffo_example, "~> 0.3.0"},
    {:diffo_example, github: "diffo-dev/diffo_example", branch: "dev"},
    {:diffo, github: "diffo-dev/diffo", branch: "dev", override: true},
    {:kino, "~> 0.14"},
    {:req, "~> 0.5"}
  ],
  config: [
    bolty: [
      {Bolt,
       [
         uri: "bolt://localhost:7687",
         auth: [username: "neo4j", password: "password"],
         user_agent: "diffoExampleLivebook/1",
         pool_size: 15,
         max_overflow: 3,
         prefix: :default,
         name: Bolt,
         log: false,
         log_hex: false
       ]}
    ]
  ],
  consolidate_protocols: false
)

Overview

Diffo is a Telecommunications Management Forum (TMF) Service and Resource Manager, built for autonomous networks. It is implemented using the Ash Framework and stores data in Neo4j via AshNeo4j.

If you are new to Diffo, start with the Diffo livebook which introduces the core Provider concepts — Specification, Instance, Feature, Characteristic, Party, Place, and Relationship.

Diffo includes a Provider Instance extension that lets you declare specialised TMF Services and Resources using a Spark DSL with very little Elixir code. The Provider Instance Extension livebook covers this in detail.

The NBN domain in this example was built entirely with that DSL — a declarative model of a realistic NBN Ethernet access hierarchy with minimal custom Elixir, derived from a short domain description. It demonstrates how much can be expressed through the Provider extension alone.

The NBN domain models a fictional NBN Ethernet access circuit and its constituent resources:

  • NbnEthernet — the parent circuit resource (identified by a PRI)
  • UNI — User Network Interface at the customer premises
  • AVC — Access Virtual Circuit (dedicated, carries traffic between UNI and CVC)
  • NTD — Network Termination Device (installed at customer premises, assigns ports to UNI)
  • CVC — Connectivity Virtual Circuit (aggregates AVCs, terminates at NNI Group)
  • NNI Group — group of NNIs at the point of interconnect
  • NNI — Network-to-Network Interface

Installing Neo4j and Configuring Bolty

Bolty is configured in the Mix.install block above — update the Neo4j credentials there if needed before evaluating.

You need Neo4j installed and running. Verify the connection:

AshNeo4j.BoltyHelper.is_connected()

It is helpful to have a Neo4j browser open locally, typically at http://localhost:7474/browser/

OPTIONAL Clear the database before starting:

AshNeo4j.Neo4jHelper.delete_all()

About NBN Co

NBN (National Broadband Network) is Australia’s wholesale fixed-line access network, operated by NBN Co. It provides standardised access products to Retail Service Providers (RSPs), who in turn deliver internet and other services to end customers.

For the purpose of this example we are going to refer to a simplified, and re-imagined NBN Co as NBN.

An RSP typically combines:

  • An NBN Ethernet access circuit (UNI + AVC) at the customer premises — the access and aggregation layer modelled in this domain
  • A home gateway device installed at the UNI, which provides the customer’s LAN, Wi-Fi, and sometimes voice
  • Transport, aggregation, and edge infrastructure connecting the NNI to the RSP’s network and on to the internet

NBN connects the customer premises to the RSP’s network via a Point of Interconnect (POI). The NNI sits at the POI, grouped into NNI Groups. AVCs carrying customer traffic are aggregated onto a CVC, which terminates at the NNI Group. The RSP purchases CVC capacity to carry the aggregate traffic of its customers at that POI.

NBN delivers over several access technologies — FTTP, FTTN, FTTB, FTTC, HFC, Fixed Wireless, and Satellite — which determine which bandwidth profiles and speeds are available to a given premises.

NBN Ethernet Technology and Speeds

The NBN domain defines Technology as an Ash Enum covering all NBN access types:

alias DiffoExample.Nbn.{Technology,Speeds}
Technology.values()

Speeds are derived from a bandwidth_profile and technology combination. For example:

Speeds.speeds(:home_fast, :FTTP)
Speeds.speeds(:home_hyperfast, :HFC)
Speeds.speeds(:wireless_superfast, :FixedWireless)
# returns :error for invalid combinations
Speeds.speeds(:home_fast, :FixedWireless)

Multi-tenancy

Each RSP operates in isolation — they can only see and manage the resources they own. This multi-tenancy is enforced at the Ash policy layer: every NBN resource is stamped with the owning RSP’s EPID at creation, and subsequent reads, updates, and destroys are scoped to the record owner.

RSP is modelled as a Party (using the Diffo.Provider.BaseParty fragment), with its EPID as the Party id. This means the rsp_id stamped on owned resources is a human-readable four-digit identifier rather than a UUID.

Select the RSP you want to operate as for the rest of this livebook. All resources you build will be owned by that RSP and isolated from resources owned by others.

alias DiffoExample.Nbn
alias DiffoExample.Nbn.Rsp
import Jason, only: [encode: 2]
DiffoExample.Nbn.Initializer.init()
rsps = Nbn.list_rsps!()
Kino.DataTable.new(rsps, keys: [:id, :name, :short_name, :state])
rsp_input = Kino.Input.select(
  "Operate as RSP",
  Enum.map(rsps, fn rsp -> {rsp.name, Atom.to_string(rsp.short_name)} end)
)
actor = Enum.find(rsps, fn rsp -> rsp.name == Kino.Input.read(rsp_input) end)
actor

Maintaining Shareable Resources

As an RSP we need maintain some shareable network resources: NNI, NNI Group, and CVC.

We’ll need these everywhere we operate, in advance of and sufficient for all the NBN Ethernet Accesses we have. We’ll just build one of each right now.

Build an NNI — the physical interconnect between the RSP and NBN:

alias DiffoExample.Nbn.{Nni, NniGroup, CVC}
nni = Nbn.build_nni!(%{}, actor: actor)
nni |> Jason.encode!(pretty: true) |> IO.puts

Build an NNI Group — a logical grouping of NNIs at a point of interconnect:

nni_group = Nbn.build_nni_group!(%{}, actor: actor)
nni_group |> Jason.encode!(pretty: true) |> IO.puts

Define the NNI Group with an SVLAN assignment and relate the NNI:

nni_group = Nbn.define_nni_group!(nni_group, %{
  characteristic_value_updates: [nni_group: [svlan: 100]]
}, actor: actor)
nni_group = Nbn.relate_nni_group!(nni_group, %{
  relationships: [%Diffo.Provider.Instance.Relationship{id: nni.id, alias: :nni, type: :isAssigned}]
}, actor: actor)
nni_group |> Jason.encode!(pretty: true) |> IO.puts

Build a CVC — the aggregation virtual circuit that terminates at the NNI Group:

cvc = Nbn.build_cvc!(%{}, actor: actor)
cvc = Nbn.relate_cvc!(cvc, %{
  relationships: [%Diffo.Provider.Instance.Relationship{id: nni_group.id, alias: :nni_group, type: :isAssigned}]
}, actor: actor)
cvc |> Jason.encode!(pretty: true) |> IO.puts

Provisioning NBN Ethernet

For each customer site we want to provide service to, we need an NBN Ethernet composite resource, involving an NTD, UNI, AVC and CVC.

The NTD is NBN infrastructure — built and managed by NBN, visible to any RSP. It may not exist at a new or existing customer site, so may be built on demand by NBN.

Build an NTD — the device installed at the customer premises:

alias DiffoExample.Nbn.{Ntd, Uni, Avc, NbnEthernet}
ntd = Nbn.build_ntd!(%{})
ntd = Nbn.define_ntd!(ntd, %{
  characteristic_value_updates: [ntd: [technology: :FTTP, ports: [1, 2, 3, 4]]]
})
ntd |> Jason.encode!(pretty: true) |> IO.puts

Build a UNI — the interface at the customer premises — and assign a port from the NTD:

uni = Nbn.build_uni!(%{})
alias Diffo.Provider.Assignment
ntd = Nbn.assign_port!(ntd, %{
  assignment: %Assignment{assignee_id: uni.id, operation: :auto_assign}
})
ntd |> Jason.encode!(pretty: true) |> IO.puts

Relate the UNI back to the NTD so it can mine technology and port from it:

uni = Nbn.relate_uni!(uni, %{
  relationships: [%Diffo.Provider.Instance.Relationship{id: ntd.id, alias: :ntd, type: :isAssigned}]
})
uni = Nbn.mine_uni!(uni, %{})
uni |> Jason.encode!(pretty: true) |> IO.puts

Build an AVC and assign it a CVLAN from the CVC:

avc = Nbn.build_avc!(%{}, actor: actor)
avc = Nbn.define_avc!(avc, %{
  characteristic_value_updates: [avc: [bandwidth_profile: :home_ultrafast]]
}, actor: actor)
cvc = Nbn.assign_cvlan!(cvc, %{
  assignment: %Assignment{assignee_id: avc.id, operation: :auto_assign}
}, actor: actor)
avc = Nbn.mine_avc!(avc, %{}, actor: actor)
avc |> Jason.encode!(pretty: true) |> IO.puts

Now build the top-level NBN Ethernet access and relate it to both the UNI and AVC:

pri = Nbn.build_nbn_ethernet!(%{}, actor: actor)
pri = Nbn.relate_nbn_ethernet!(pri, %{
  relationships: [
    %Diffo.Provider.Instance.Relationship{id: uni.id, alias: :uni, type: :isAssigned},
    %Diffo.Provider.Instance.Relationship{id: avc.id, alias: :avc, type: :isAssigned}
  ]
}, actor: actor)
pri = Nbn.mine_nbn_ethernet!(pri, %{}, actor: actor)
pri |> Jason.encode!(pretty: true) |> IO.puts

The mine action on NbnEthernet extracts technology from the UNI and bandwidth_profile from the AVC and derives the speeds automatically.

Exploring the Graph

You can query all nodes and relationships in Neo4j browser with:

MATCH (n1)-[r]->(n2) RETURN r, n1, n2 LIMIT 50

Or from Elixir:

AshNeo4j.Cypher.run("MATCH (n1)-[r]->(n2) RETURN r, n1, n2 LIMIT 50")

JSON API

The NBN domain exposes a JSON API via Plug.Cowboy on port 4000. Start the server in your application before evaluating these cells.

First check the catalog — all NBN specifications are initialised on startup:

Req.get!("http://localhost:4000/catalog", decode_body: false).body |> IO.puts()

Now retrieve all NBN Ethernet instances:

Req.get!("http://localhost:4000/nbnEthernet", decode_body: false).body |> IO.puts()

Or fetch the one we provisioned above by id:

Req.get!("http://localhost:4000/nbnEthernet/#{pri.id}", decode_body: false).body |> IO.puts()

What Next?

You’ve provisioned a complete NBN Ethernet access — NTD, UNI, AVC, CVC, NNI Group, and NNI — and seen how the mine actions propagate technology, speeds, CVLAN and port assignments up the resource hierarchy automatically.

The Access domain in diffo_example shows a similar pattern for DSL access services. Explore lib/access/ for copper-network equivalents (Cable, Card, Path, Shelf).

If you find Diffo useful please visit and star on GitHub.